├── dev-requirements.txt
├── pymorton
├── __init__.py
└── pymorton.py
├── .gitignore
├── .travis.yml
├── .coveragerc
├── LICENSE.md
├── setup.py
├── tests
└── test_pymorton.py
└── README.md
/dev-requirements.txt:
--------------------------------------------------------------------------------
1 | nose==1.3.7
2 |
--------------------------------------------------------------------------------
/pymorton/__init__.py:
--------------------------------------------------------------------------------
1 | from .pymorton import *
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.DS_Store
2 | *.pyc
3 | build/
4 | dist/
5 | *.egg-info/
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "2.7"
4 | - "3.4"
5 | - "3.5"
6 | - "3.6"
7 | - "3.7"
8 |
9 | install:
10 | - pip install -r dev-requirements.txt
11 | - python setup.py install
12 |
13 | script:
14 | - nosetests
15 |
--------------------------------------------------------------------------------
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | branch = True
3 | source = pymorton
4 |
5 | [report]
6 | exclude_lines =
7 | DIVISORS
8 | # pymorton (https://github.com/trevorprater/pymorton)
9 | # Author: trevor.prater@gmail.com
10 | # License: MIT
11 | print
12 |
13 | ignore_errors = False
14 | show_missing = True
15 | omit =
16 | tests/*
17 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright © 2015-2018, Trevor Prater, trevor.prater@gmail.com
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | LONG_DESCRIPTION = """
4 | Ordinal hashing of multidimensonal data and geographic coordinates via Morton coding / Z-ordering.
5 |
6 | In mathematical analysis and computer science, `Z-order`, `Morton-order`, or a `Morton-code` is a function
7 | which maps multidimensional data to one dimension while preserving locality of the data points.
8 | It was introduced in 1966 by IBM researcher, G. M. Morton. The z-value of a point in multidimensions is
9 | calculated by interleaving the binary representations of its coordinate values. Once the data are sorted
10 | into this ordering, any one-dimensional data structure can be used, such as binary search trees, B-trees,
11 | skip lists, or hash tables. The resulting ordering can equivalently be described as the order one would
12 | achieve from a depth-first traversal of a quadtree, where `{x, y, ..., K}` are combined into a single
13 | ordinal value that is easily compared, searched, and indexed against other Morton numbers.
14 | """
15 |
16 |
17 | def build():
18 | setup(
19 | name='pymorton',
20 | version='1.0.7',
21 | author='Trevor Prater',
22 | author_email='trevor.prater@gmail.com',
23 | description='A lightweight morton coder with lat/long support.',
24 | long_description=LONG_DESCRIPTION,
25 | license='MIT',
26 | keywords='nearest neighbors, geo hashing, geo, z-order, morton coding, hashing',
27 | url='https://github.com/trevorprater/pymorton',
28 | packages=['pymorton'],
29 | python_requires='>=2.6',
30 | install_requires=[],
31 | classifiers=[
32 | 'Development Status :: 5 - Production/Stable',
33 | 'Topic :: Utilities',
34 | 'License :: OSI Approved :: MIT License'
35 | ]
36 | )
37 |
38 |
39 | if __name__ == '__main__':
40 | build()
41 |
--------------------------------------------------------------------------------
/tests/test_pymorton.py:
--------------------------------------------------------------------------------
1 | # pymorton (https://github.com/trevorprater/pymorton)
2 | # Author: trevor.prater@gmail.com
3 | # License: MIT
4 |
5 | import unittest
6 | import random
7 | import sys
8 | import pymorton as pm
9 | from nose.tools import assert_raises
10 |
11 |
12 | class TestOrdinalHashing(unittest.TestCase):
13 |
14 | def test_hashing_2d_valid(self):
15 | assert pm.interleave(100, 50) == pm.interleave2(100, 50)
16 |
17 | def test_hashing_3d_valid(self):
18 | assert pm.interleave(10, 50, 40) == pm.interleave3(10, 50, 40)
19 |
20 | def test_hash_reversability_2d_valid(self):
21 | assert (100, 30) == pm.deinterleave2(pm.interleave(100, 30))
22 | if getattr(sys, 'maxint', 0) and sys.maxint <= 2 ** 31 - 1:
23 | max_v = 0x0fff
24 | else:
25 | max_v = 0x0fffffff
26 | for i in range(100):
27 | p1 = (random.randint(0, max_v), random.randint(0, max_v))
28 | print(p1, pm.deinterleave2(pm.interleave(*p1)))
29 | assert p1 == pm.deinterleave2(pm.interleave(*p1))
30 |
31 | def test_hash_reversability_3d_valid(self):
32 | assert pm.deinterleave3(pm.interleave(100, 30, 50)) == (100, 30, 50)
33 | if getattr(sys, 'maxint', 0) and sys.maxint <= 2 ** 31 - 1:
34 | max_v = 0xff
35 | else:
36 | max_v = 0xffff
37 | for i in range(100):
38 | p1 = (random.randint(0, max_v), random.randint(0, max_v), random.randint(0, max_v))
39 | print(p1, pm.deinterleave3(pm.interleave(*p1)))
40 | assert p1 == pm.deinterleave3(pm.interleave(*p1))
41 |
42 | def test_hash_ordinality_2d(self):
43 | assert pm.interleave(10, 25) < pm.interleave(10, 50)
44 |
45 | def test_hash_ordinality_3d(self):
46 | assert pm.interleave(10, 25, 50) < pm.interleave(10, 25, 100)
47 |
48 | def test_interleave2_input_length_invalid(self):
49 | assert_raises(ValueError, pm.interleave2, 74)
50 |
51 | def test_interleave2_input_type_invalid(self):
52 | assert_raises(ValueError, pm.interleave2, 78, "73")
53 |
54 | def test_interleave3_input_length_invalid(self):
55 | assert_raises(ValueError, pm.interleave3, 78, 73)
56 |
57 | def test_interleave3_input_type_invalid(self):
58 | assert_raises(ValueError, pm.interleave3, 78, 77, "73")
59 |
60 | def test_deinterleave2_input_type_invalid(self):
61 | assert_raises(ValueError, pm.deinterleave2, "73")
62 |
63 | def test_deinterleave3_input_type_invalid(self):
64 | assert_raises(ValueError, pm.deinterleave3, "73")
65 |
66 | def test_interleave_input_length_invalid(self):
67 | assert_raises(ValueError, pm.interleave, 77)
68 |
69 |
70 | class TestGeoHashing(unittest.TestCase):
71 |
72 | def test_standard_geohashing(self):
73 | lat, lng = 40.712014, -74.008164
74 | latlng_morton = pm.interleave_latlng(lat, lng)
75 | assert '03023211232311330231120312032231' == latlng_morton
76 | assert pm.deinterleave_latlng(latlng_morton) == (40.712014, -74.008164)
77 |
78 | def test_invalid_positive_longitude(self):
79 | lat, lng = 40.712013, 190.008164
80 | assert pm.deinterleave_latlng(pm.interleave_latlng(lat, lng)) == (lat, round(lng - 180.0, 6))
81 |
82 | def test_invalid_negative_longitude(self):
83 | lat, lng = -40.712013, -190.008164
84 | assert pm.deinterleave_latlng(pm.interleave_latlng(lat, lng)) == (lat, round(lng + 180.0, 6))
85 |
86 | def test_invalid_positive_latitude(self):
87 | lat, lng = 220.712013, -74.008164
88 | assert pm.deinterleave_latlng(pm.interleave_latlng(lat, lng)) == (round(lat - 180.0, 6), lng)
89 |
90 | def test_invalid_negative_latitude(self):
91 | lat, lng = -220.712013, -74.008164
92 | assert pm.deinterleave_latlng(pm.interleave_latlng(lat, lng)) == (round(lat + 180.0, 6), lng)
93 |
94 | def test_non_float_input_interleave_latlng(self):
95 | lat, lng = -220.712013, "-74.008164"
96 | assert_raises(ValueError, pm.interleave_latlng, lat, lng)
97 |
98 | def test_geohash_ordinality(self):
99 | assert pm.interleave_latlng(-40.723471, -73.985361) < pm.interleave_latlng(-40.523471, -73.785361)
100 |
101 |
102 | if __name__ == '__main__':
103 | unittest.main()
104 |
105 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
pymorton
2 |
3 | Ordinal hashing of multidimensonal data and geographic coordinates via Morton coding / Z-ordering.
4 |
5 | # [](https://codecov.io/gh/trevorprater/pymorton) [](https://travis-ci.org/trevorprater/pymorton) [](https://travis-ci.org/trevorprater/pymorton) []() [](https://github.com/trevorprater/pymorton/blob/master/LICENSE.md)
6 |
7 |
8 |
9 |
10 |
11 | In mathematical analysis and computer science, *Z-order*, *Morton-order*, or a *Morton-code* is a function which maps multidimensional data to one dimension while preserving locality of the data points. It was introduced in 1966 by IBM researcher, *[G. M. Morton](https://domino.research.ibm.com/library/cyberdig.nsf/papers/0DABF9473B9C86D48525779800566A39/$File/Morton1966.pdf)*. *The z-value* of a point in multidimensions is calculated by interleaving the binary representations of its coordinate values. Once the data are sorted into this ordering, any one-dimensional data structure can be used, such as binary search trees, B-trees, skip lists, or hash tables. The resulting ordering can equivalently be described as the order one would achieve from a depth-first traversal of a quadtree,
12 | where `{x, y, ..., K}` are combined into a single ordinal value that is easily compared, searched, and indexed against other *Morton numbers*.
13 |
14 |
15 | *At the highest level, **pymorton** is split into two logical functions*:
16 |
17 | * **(de)interleave**: encodes/decodes hashes representing two or three dimensionsal integer sets. `{x, y, z ∈ Z}` or `{x, y ∈ Z}`, where `Z` represents all integer values.
18 |
19 | * **(de)interleave_latlng**: encodes and decodes hashes representing latitude and longitude.
20 |
21 |
22 |
23 | ### Example usage scenario:
24 | * *Given a directory of images, **sort the images by color** (average RGB)*:
25 |
26 |
27 | ```python
28 | from statistics import mean
29 | from glob import glob
30 | from PIL import Image
31 | import pymorton
32 |
33 | imgs = [(fname, Image.open(fname)) for fname in glob('imgpath/*.jpg')[:100]]
34 |
35 | # for each image, generate a tuple of len==3, representing the image's average RGB value
36 | avg_rgb_values = [
37 | [int(mean(img.getdata(band))) for band in range(3)] for _, img in imgs]
38 |
39 | # using the average RGB values, compute the Z-order of each image
40 | hashed_imgs = list(zip([fname for fname, _ in imgs],
41 | [pymorton.interleave(*avg_rgb) for avg_rgb in avg_rgb_values]))
42 |
43 | # returns a sorted-by-color list of photos found within the directory
44 | return sorted(hashed_imgs, key=lambda img_tuple: img_tuple[1])
45 | ```
46 |
47 | While the above use-case is fairly uncommon in the context of *Morton-coding*, I believe it illustrates the utility of the algorithm quite well. *Morton-coding* is most commonly used within the realm of geospatial indexing, but its potential applications are infinite!
48 |
49 |
50 | ## Installation
51 |
52 | via [pip](https://pypi.python.org/pypi/pymorton/0.1.0):
53 | ```bash
54 | pip install pymorton
55 | ```
56 |
57 |
58 | via [source](https://github.com/trevorprater/pymorton):
59 | ```bash
60 | git clone https://github.com/trevorprater/pymorton.git
61 | cd pymorton
62 | python setup.py install
63 | ```
64 |
65 |
66 | ## Usage
67 |
68 | * **3D-hashing**
69 | ```python
70 | import pymorton as pm
71 |
72 | mortoncode = pm.interleave(100, 200, 50) # 5162080
73 | mortoncode = pm.interleave3(100, 200, 50) # 5162080
74 |
75 | pm.deinterleave3(mortoncode) # (100, 200, 50)
76 | ```
77 |
78 |
79 | * **2D-hashing**
80 | ```python
81 | import pymorton as pm
82 |
83 | mortoncode = pm.interleave(100, 200) # 46224
84 | mortoncode = pm.interleave2(100, 200) # 46224
85 |
86 | pm.deinterleave2(mortoncode) # (100, 200)
87 | ```
88 |
89 |
90 | * **geo-hashing**
91 | ```python
92 | import pymorton as pm
93 |
94 | geohash = pm.interleave_latlng(40.723471, -73.985361) # '03023211233202130332202203002303'
95 |
96 | pm.deinterleave_latlng(geohash) # (40.723470943048596, -73.98536103777587)
97 | ```
98 |
99 |
100 | ## API
101 | - `pymorton.interleave(*args)`
102 | * Hashes `x, y` or `x, y, z` into a single value.
103 | This function wraps interleave2() and interleave3() by supporting variable-length args.
104 |
105 | - `pymorton.interleave2(x, y)`
106 | * Returns a hash (int) representing `x, y`.
107 |
108 | - `pymorton.interleave3(x, y, z)`
109 | * Returns a hash (int) representing `x, y, z`.
110 |
111 | - `pymorton.interleave_latlng(lat, lng)`
112 | * Returns a hash (string base-4)
113 | representing `lat, lng`.
114 |
115 | - `pymorton.deinterleave2(hash)`
116 | * Returns a tuple representing the arguments to
117 | the corresponding interleave2() call.
118 |
119 | - `pymorton.deinterleave3(hash)`
120 | * Returns a tuple representing the arguments to
121 | the corresponding interleave3() call.
122 |
123 | - `pymorton.deinterleave_latlng(hash)`
124 | * Returns a tuple representing the arguments to
125 | the corresponding interleave_latlng() call.
126 |
127 |
128 |
129 | ## Tests
130 |
131 | From the project's root directory, execute `nosetests`.
132 |
133 | Please feel free to contact *trevor.prater@gmail.com* regarding any questions/comments/issues.
134 |
135 |
136 | ### References:
137 |
138 | * [Z-order curve](https://en.wikipedia.org/wiki/Z-order_curve)
139 | * [Implementation for the algorithm (1)](http://stackoverflow.com/a/18528775)
140 | * [Implementation for the algorithm (2)](https://github.com/Forceflow/libmorton)
141 | * [Extended explanation with different algorithms](http://www.forceflow.be/2013/10/07/morton-encodingdecoding-through-bit-interleaving-implementations/)
142 |
143 |
144 | ## License
145 | MIT
146 |
--------------------------------------------------------------------------------
/pymorton/pymorton.py:
--------------------------------------------------------------------------------
1 | # pymorton (https://github.com/trevorprater/pymorton)
2 | # Author: trevor.prater@gmail.com
3 | # License: MIT
4 |
5 | import sys
6 |
7 | _DIVISORS = [180.0 / 2 ** n for n in range(32)]
8 |
9 |
10 | def __part1by1_32(n):
11 | n &= 0x0000ffff # base10: 65535, binary: 1111111111111111, len: 16
12 | n = (n | (n << 8)) & 0x00FF00FF # base10: 16711935, binary: 111111110000000011111111, len: 24
13 | n = (n | (n << 4)) & 0x0F0F0F0F # base10: 252645135, binary: 1111000011110000111100001111, len: 28
14 | n = (n | (n << 2)) & 0x33333333 # base10: 858993459, binary: 110011001100110011001100110011, len: 30
15 | n = (n | (n << 1)) & 0x55555555 # base10: 1431655765, binary: 1010101010101010101010101010101, len: 31
16 |
17 | return n
18 |
19 |
20 | def __part1by2_32(n):
21 | n &= 0x000003ff # base10: 1023, binary: 1111111111, len: 10
22 | n = (n ^ (n << 16)) & 0xff0000ff # base10: 4278190335, binary: 11111111000000000000000011111111, len: 32
23 | n = (n ^ (n << 8)) & 0x0300f00f # base10: 50393103, binary: 11000000001111000000001111, len: 26
24 | n = (n ^ (n << 4)) & 0x030c30c3 # base10: 51130563, binary: 11000011000011000011000011, len: 26
25 | n = (n ^ (n << 2)) & 0x09249249 # base10: 153391689, binary: 1001001001001001001001001001, len: 28
26 |
27 | return n
28 |
29 |
30 | def __unpart1by1_32(n):
31 | n &= 0x55555555 # base10: 1431655765, binary: 1010101010101010101010101010101, len: 31
32 | n = (n ^ (n >> 1)) & 0x33333333 # base10: 858993459, binary: 110011001100110011001100110011, len: 30
33 | n = (n ^ (n >> 2)) & 0x0f0f0f0f # base10: 252645135, binary: 1111000011110000111100001111, len: 28
34 | n = (n ^ (n >> 4)) & 0x00ff00ff # base10: 16711935, binary: 111111110000000011111111, len: 24
35 | n = (n ^ (n >> 8)) & 0x0000ffff # base10: 65535, binary: 1111111111111111, len: 16
36 |
37 | return n
38 |
39 |
40 | def __unpart1by2_32(n):
41 | n &= 0x09249249 # base10: 153391689, binary: 1001001001001001001001001001, len: 28
42 | n = (n ^ (n >> 2)) & 0x030c30c3 # base10: 51130563, binary: 11000011000011000011000011, len: 26
43 | n = (n ^ (n >> 4)) & 0x0300f00f # base10: 50393103, binary: 11000000001111000000001111, len: 26
44 | n = (n ^ (n >> 8)) & 0xff0000ff # base10: 4278190335, binary: 11111111000000000000000011111111, len: 32
45 | n = (n ^ (n >> 16)) & 0x000003ff # base10: 1023, binary: 1111111111, len: 10
46 |
47 | return n
48 |
49 |
50 | def __part1by1_64(n):
51 | n &= 0x00000000ffffffff # binary: 11111111111111111111111111111111, len: 32
52 | n = (n | (n << 16)) & 0x0000FFFF0000FFFF # binary: 1111111111111111000000001111111111111111, len: 40
53 | n = (n | (n << 8)) & 0x00FF00FF00FF00FF # binary: 11111111000000001111111100000000111111110000000011111111, len: 56
54 | n = (n | (n << 4)) & 0x0F0F0F0F0F0F0F0F # binary: 111100001111000011110000111100001111000011110000111100001111, len: 60
55 | n = (n | (n << 2)) & 0x3333333333333333 # binary: 11001100110011001100110011001100110011001100110011001100110011, len: 62
56 | n = (n | (n << 1)) & 0x5555555555555555 # binary: 101010101010101010101010101010101010101010101010101010101010101, len: 63
57 |
58 | return n
59 |
60 |
61 | def __part1by2_64(n):
62 | n &= 0x1fffff # binary: 111111111111111111111, len: 21
63 | n = (n | (n << 32)) & 0x1f00000000ffff # binary: 11111000000000000000000000000000000001111111111111111, len: 53
64 | n = (n | (n << 16)) & 0x1f0000ff0000ff # binary: 11111000000000000000011111111000000000000000011111111, len: 53
65 | n = (n | (n << 8)) & 0x100f00f00f00f00f # binary: 1000000001111000000001111000000001111000000001111000000001111, len: 61
66 | n = (n | (n << 4)) & 0x10c30c30c30c30c3 # binary: 1000011000011000011000011000011000011000011000011000011000011, len: 61
67 | n = (n | (n << 2)) & 0x1249249249249249 # binary: 1001001001001001001001001001001001001001001001001001001001001, len: 61
68 |
69 | return n
70 |
71 |
72 | def __unpart1by1_64(n):
73 | n &= 0x5555555555555555 # binary: 101010101010101010101010101010101010101010101010101010101010101, len: 63
74 | n = (n ^ (n >> 1)) & 0x3333333333333333 # binary: 11001100110011001100110011001100110011001100110011001100110011, len: 62
75 | n = (n ^ (n >> 2)) & 0x0f0f0f0f0f0f0f0f # binary: 111100001111000011110000111100001111000011110000111100001111, len: 60
76 | n = (n ^ (n >> 4)) & 0x00ff00ff00ff00ff # binary: 11111111000000001111111100000000111111110000000011111111, len: 56
77 | n = (n ^ (n >> 8)) & 0x0000ffff0000ffff # binary: 1111111111111111000000001111111111111111, len: 40
78 | n = (n ^ (n >> 16)) & 0x00000000ffffffff # binary: 11111111111111111111111111111111, len: 32
79 | return n
80 |
81 |
82 | def __unpart1by2_64(n):
83 | n &= 0x1249249249249249 # binary: 1001001001001001001001001001001001001001001001001001001001001, len: 61
84 | n = (n ^ (n >> 2)) & 0x10c30c30c30c30c3 # binary: 1000011000011000011000011000011000011000011000011000011000011, len: 61
85 | n = (n ^ (n >> 4)) & 0x100f00f00f00f00f # binary: 1000000001111000000001111000000001111000000001111000000001111, len: 61
86 | n = (n ^ (n >> 8)) & 0x1f0000ff0000ff # binary: 11111000000000000000011111111000000000000000011111111, len: 53
87 | n = (n ^ (n >> 16)) & 0x1f00000000ffff # binary: 11111000000000000000000000000000000001111111111111111, len: 53
88 | n = (n ^ (n >> 32)) & 0x1fffff # binary: 111111111111111111111, len: 21
89 | return n
90 |
91 |
92 | if getattr(sys, 'maxint', 0) and sys.maxint <= 2 ** 31 - 1:
93 | __part1by1 = __part1by1_32
94 | __part1by2 = __part1by2_32
95 | __unpart1by1 = __unpart1by1_32
96 | __unpart1by2 = __unpart1by2_32
97 | else:
98 | __part1by1 = __part1by1_64
99 | __part1by2 = __part1by2_64
100 | __unpart1by1 = __unpart1by1_64
101 | __unpart1by2 = __unpart1by2_64
102 |
103 |
104 | def interleave2(*args):
105 | if len(args) != 2:
106 | raise ValueError('Usage: interleave2(x, y)')
107 | for arg in args:
108 | if not isinstance(arg, int):
109 | print('Usage: interleave2(x, y)')
110 | raise ValueError("Supplied arguments contain a non-integer!")
111 |
112 | return __part1by1(args[0]) | (__part1by1(args[1]) << 1)
113 |
114 |
115 | def interleave3(*args):
116 | if len(args) != 3:
117 | raise ValueError('Usage: interleave3(x, y, z)')
118 | for arg in args:
119 | if not isinstance(arg, int):
120 | print('Usage: interleave3(x, y, z)')
121 | raise ValueError("Supplied arguments contain a non-integer!")
122 |
123 | return __part1by2(args[0]) | (__part1by2(args[1]) << 1) | (
124 | __part1by2(args[2]) << 2)
125 |
126 |
127 | def interleave(*args):
128 | if len(args) < 2 or len(args) > 3:
129 | print('Usage: interleave(x, y, (optional) z)')
130 | raise ValueError(
131 | "You must supply two or three integers to interleave!")
132 |
133 | method = globals()["interleave" + str(len(args))]
134 |
135 | return method(*args)
136 |
137 |
138 | def deinterleave2(n):
139 | if not isinstance(n, int):
140 | print('Usage: deinterleave2(n)')
141 | raise ValueError("Supplied arguments contain a non-integer!")
142 |
143 | return __unpart1by1(n), __unpart1by1(n >> 1)
144 |
145 |
146 | def deinterleave3(n):
147 | if not isinstance(n, int):
148 | print('Usage: deinterleave2(n)')
149 | raise ValueError("Supplied arguments contain a non-integer!")
150 |
151 | return __unpart1by2(n), __unpart1by2(n >> 1), __unpart1by2(n >> 2)
152 |
153 | def interleave_latlng(lat, lng):
154 | if not isinstance(lat, float) or not isinstance(lng, float):
155 | print('Usage: interleave_latlng(float, float)')
156 | raise ValueError("Supplied arguments must be of type float!")
157 |
158 | if (lng > 180):
159 | x = (lng % 180) + 180.0
160 | elif (lng < -180):
161 | x = (-((-lng) % 180)) + 180.0
162 | else:
163 | x = lng + 180.0
164 | if (lat > 90):
165 | y = (lat % 90) + 90.0
166 | elif (lat < -90):
167 | y = (-((-lat) % 90)) + 90.0
168 | else:
169 | y = lat + 90.0
170 |
171 | morton_code = ""
172 | for dx in _DIVISORS:
173 | digit = 0
174 | if (y >= dx):
175 | digit |= 2
176 | y -= dx
177 | if (x >= dx):
178 | digit |= 1
179 | x -= dx
180 | morton_code += str(digit)
181 |
182 | return morton_code
183 |
184 |
185 | def deinterleave_latlng(n):
186 | x = y = 0
187 | for (digit, multiplier) in zip([int(d) for d in n], _DIVISORS):
188 | if (digit & 2):
189 | y += multiplier
190 | if (digit & 1):
191 | x += multiplier
192 |
193 | return round(y - 90.0, 6), round(x - 180.0, 6)
194 |
195 |
--------------------------------------------------------------------------------