├── 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 | #
[![Codecov](https://img.shields.io/codecov/c/github/trevorprater/pymorton.svg)](https://codecov.io/gh/trevorprater/pymorton) [![Travis](https://img.shields.io/travis/trevorprater/pymorton.svg)](https://travis-ci.org/trevorprater/pymorton) [![Status](https://img.shields.io/badge/status-stable-brightgreen.svg)](https://travis-ci.org/trevorprater/pymorton) [![GitHub tag](https://img.shields.io/github/tag/trevorprater/pymorton.svg)]() [![License](https://img.shields.io/badge/license-MIT-blue.svg)](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 | --------------------------------------------------------------------------------