├── .gitignore ├── LICENSE ├── MANIFEST.IN ├── README.md ├── benchmark.py ├── setup.py └── src ├── FastLine.cpp └── Line.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | .vscode 132 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 MrGolden1 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.IN: -------------------------------------------------------------------------------- 1 | graft src/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FastLine 2 | 3 | **FastLine** is a Python module for performing geometric line operations. Implemented in C++ and bound to Python, it is optimized for speed, making it suitable for projects that require numerous line calculations. 4 | 5 | ## Installation 6 | 7 | You can install FastLine using `pip`: 8 | 9 | ```bash 10 | pip install FastLine 11 | ``` 12 | 13 | Alternatively, install it directly from the source repository: 14 | 15 | ```bash 16 | git clone https://github.com/MrGolden1/FastLine.git 17 | cd FastLine 18 | pip install . 19 | ``` 20 | 21 | ## Usage 22 | 23 | Import the `Line` class from the FastLine module and create line instances using either two points or slope-intercept form. 24 | 25 | ```python 26 | from FastLine import Line 27 | 28 | # Define a line by two points 29 | line1 = Line(p1=(0, 0), p2=(10, 10)) 30 | 31 | # Define a line by its slope and intercept 32 | line2 = Line(m=4, b=-1) 33 | ``` 34 | 35 | ### Methods 36 | 37 | #### `solve` 38 | 39 | Calculates the corresponding y for a given x or x for a given y. 40 | 41 | ```python 42 | >>> line1.solve(x=20) 43 | 20.0 44 | 45 | >>> line2.solve(y=20) 46 | 5.25 47 | ``` 48 | 49 | #### `distance_to` 50 | 51 | Computes the shortest distance from a given point to the line. 52 | 53 | ```python 54 | >>> line1.distance_to((20, 50)) 55 | 21.213203435596427 56 | 57 | >>> line2.distance_to((-15, 17)) 58 | 18.91777875283397 59 | ``` 60 | 61 | #### `crossed_by` 62 | 63 | Determines the side of the line on which a point lies. Returns `-1`, `0`, or `1` indicating the opposite side, on the line, or a specific side, respectively. You will need to define the orientation of each side based on your application's context. 64 | 65 | ```python 66 | >>> line1.crossed_by((20, 50)) # Specific side 67 | 1 68 | 69 | >>> line1.crossed_by((50, 50)) # On the line 70 | 0 71 | 72 | >>> line1.crossed_by((-20, -50)) # Opposite side 73 | -1 74 | ``` 75 | 76 | #### `intersection` 77 | 78 | Finds the intersection point with another line. Returns `None` if the lines are parallel. 79 | 80 | ```python 81 | >>> line1.intersection(line2) 82 | (0.3333333333333333, 0.3333333333333333) 83 | ``` 84 | 85 | ## Acknowledgements 86 | 87 | - [Intersection of Two Lines](https://stackoverflow.com/a/3838398/10220190) 88 | - [Distance from a Point to a Line](https://stackoverflow.com/a/39840218/10220190) 89 | - [Line Side Determination](https://stackoverflow.com/a/20679579/10220190) 90 | 91 | ## Author 92 | 93 | **M. Ali Zarrinzadeh** 94 | 95 | - Email: [ali.zarrinzadeh@gmail.com](mailto:ali.zarrinzadeh@gmail.com) 96 | 97 | ## Speed Benchmarks 98 | 99 | FastLine has been benchmarked against pure Python, NumPy, and Numba implementations to evaluate its performance in checking intersections between M and N line segments. 100 | 101 | ### Running the Benchmarks 102 | 103 | 1. **Install Dependencies:** 104 | 105 | Ensure you have the necessary dependencies installed: 106 | 107 | ```bash 108 | pip install numpy numba 109 | ``` 110 | 111 | 2. **Execute the Benchmark Script:** 112 | 113 | Run the benchmark script to compare performance: 114 | 115 | ```bash 116 | python benchmark.py 117 | ``` 118 | 119 | ### Benchmark Results 120 | 121 | The following table summarizes the benchmark results comparing FastLine with pure Python, NumPy, and Numba implementations: 122 | 123 | | Implementation | Time (seconds) | 124 | |------------------------------------|----------------| 125 | | **FastLine** | 0.1523 | 126 | | Pure Python | 0.4266 | 127 | | NumPy (Non-Vectorized) | 2.8718 | 128 | | Numba | 0.6909 | 129 | 130 | --- 131 | 132 | For any questions or contributions, feel free to contact the author or submit an issue on the [GitHub repository](https://github.com/MrGolden1/FastLine). -------------------------------------------------------------------------------- /benchmark.py: -------------------------------------------------------------------------------- 1 | import time 2 | import numpy as np 3 | import numba 4 | from FastLine import Line 5 | 6 | def benchmark_fastline(): 7 | start_time = time.time() 8 | l1 = Line(p1=(0, 0), p2=(10, 10)) 9 | l2 = Line(m=4, b=-1) 10 | for _ in range(1000000): 11 | l1.intersection(l2) 12 | end_time = time.time() 13 | return end_time - start_time 14 | 15 | def benchmark_pure_python(): 16 | def intersection(l1, l2): 17 | x1, y1, x2, y2 = l1 18 | x3, y3, x4, y4 = l2 19 | denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) 20 | if denom == 0: 21 | return None 22 | px = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / denom 23 | py = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / denom 24 | return px, py 25 | 26 | start_time = time.time() 27 | l1 = (0, 0, 10, 10) 28 | l2 = (0, -1, 10, 39) 29 | for _ in range(1000000): 30 | intersection(l1, l2) 31 | end_time = time.time() 32 | return end_time - start_time 33 | 34 | def benchmark_numpy(): 35 | def intersection(l1, l2): 36 | x1, y1, x2, y2 = l1 37 | x3, y3, x4, y4 = l2 38 | denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) 39 | if denom == 0: 40 | return None 41 | px = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / denom 42 | py = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / denom 43 | return px, py 44 | 45 | start_time = time.time() 46 | l1 = np.array([0, 0, 10, 10]) 47 | l2 = np.array([0, -1, 10, 39]) 48 | for _ in range(1000000): 49 | intersection(l1, l2) 50 | end_time = time.time() 51 | return end_time - start_time 52 | 53 | @numba.jit(nopython=True) 54 | def numba_intersection(l1, l2): 55 | x1, y1, x2, y2 = l1 56 | x3, y3, x4, y4 = l2 57 | denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) 58 | if denom == 0: 59 | return None 60 | px = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / denom 61 | py = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / denom 62 | return px, py 63 | 64 | def benchmark_numba(): 65 | start_time = time.time() 66 | l1 = np.array([0, 0, 10, 10]) 67 | l2 = np.array([0, -1, 10, 39]) 68 | for _ in range(1000000): 69 | numba_intersection(l1, l2) 70 | end_time = time.time() 71 | return end_time - start_time 72 | 73 | def main(): 74 | print("Benchmarking FastLine...") 75 | fastline_time = benchmark_fastline() 76 | print(f"FastLine: {fastline_time:.4f} seconds") 77 | 78 | print("Benchmarking Pure Python...") 79 | pure_python_time = benchmark_pure_python() 80 | print(f"Pure Python: {pure_python_time:.4f} seconds") 81 | 82 | print("Benchmarking NumPy...") 83 | numpy_time = benchmark_numpy() 84 | print(f"NumPy: {numpy_time:.4f} seconds") 85 | 86 | print("Benchmarking Numba...") 87 | numba_time = benchmark_numba() 88 | print(f"Numba: {numba_time:.4f} seconds") 89 | 90 | if __name__ == "__main__": 91 | main() 92 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup, Extension 2 | import os 3 | 4 | # change compiler to gcc 5 | os.environ['CC'] = 'gcc' 6 | 7 | src_dir = 'src' 8 | ext_modules = [Extension('FastLine', 9 | [src_dir + '/FastLine.cpp'], 10 | language='c++', 11 | ), ] 12 | 13 | setup(name='FastLine', 14 | version='1.0', 15 | ext_modules=ext_modules, 16 | author='M.Ali Zarrinzade', 17 | author_email="ali.zarrinzadeh@gmail.com", 18 | description="FastLine", 19 | long_description=open("README.md").read(), 20 | long_description_content_type="text/markdown", 21 | license="MIT", 22 | classifiers=[ 23 | 'Development Status :: 5 - Production/Stable', 24 | 'Intended Audience :: Developers', 25 | 'Intended Audience :: Science/Research', 26 | 'Programming Language :: Python :: 3', 27 | ], 28 | url="https://github.com/MrGolden1/FastLine", 29 | project_urls={ 30 | "Bug Tracker": "https://github.com/MrGolden1/FastLine/issues", 31 | }, 32 | ) 33 | -------------------------------------------------------------------------------- /src/FastLine.cpp: -------------------------------------------------------------------------------- 1 | #define PY_SSIZE_T_CLEAN 2 | #include 3 | #include "Line.h" 4 | 5 | typedef struct 6 | { 7 | PyObject_HEAD 8 | // define the attributes of the class 9 | Line *line; 10 | } Py_Line; 11 | 12 | static PyObject *Line_new(PyTypeObject *type, PyObject *args, PyObject *kwds); 13 | static int Line_init(Py_Line *self, PyObject *args, PyObject *kwds); 14 | static void Line_dealloc(Py_Line *self); 15 | static PyObject *Line_solve(Py_Line *self, PyObject *args, PyObject *kwds); 16 | static PyObject *Line_crossed_by(Py_Line *self, PyObject *args); 17 | static PyObject *Line_distance_to(Py_Line *self, PyObject *args); 18 | static PyObject *Line_intersection(Py_Line *self, PyObject *args); 19 | static PyObject *Line_get_points(Py_Line *self, void *closure); 20 | static PyObject *Line_get_m(Py_Line *self, void *closure); 21 | static PyObject *Line_get_b(Py_Line *self, void *closure); 22 | static PyObject *Line_get_p1(Py_Line *self, void *closure); 23 | static PyObject *Line_get_p2(Py_Line *self, void *closure); 24 | 25 | /* Line methods */ 26 | static PyMethodDef Py_Line_methods[] = { 27 | {"solve", (PyCFunction)Line_solve, METH_KEYWORDS | METH_VARARGS, "Solve for x or y."}, 28 | {"crossed_by", (PyCFunction)Line_crossed_by, METH_VARARGS, "Check if the line is crossed by a point. return [-1,0,1]."}, 29 | {"distance_to", (PyCFunction)Line_distance_to, METH_VARARGS, "Get the distance to a point."}, 30 | {"intersection", (PyCFunction)Line_intersection, METH_VARARGS, "Get the intersection point of two lines."}, 31 | {"get_points", (PyCFunction)Line_get_points, METH_NOARGS, "Get the two points of the line."}, 32 | {"get_m", (PyCFunction)Line_get_m, METH_NOARGS, "Get the slope of the line."}, 33 | {"get_b", (PyCFunction)Line_get_b, METH_NOARGS, "Get the y-intercept of the line."}, 34 | {"get_p1", (PyCFunction)Line_get_p1, METH_NOARGS, "Get the first point of the line."}, 35 | {"get_p2", (PyCFunction)Line_get_p2, METH_NOARGS, "Get the second point of the line."}, 36 | {NULL} /* Sentinel */ 37 | }; 38 | 39 | /* Line docstring */ 40 | static char Py_Line_doc[] = "Line class."; 41 | 42 | /* __Str__ method */ 43 | static PyObject *Line_str(Py_Line *self) 44 | { 45 | return PyUnicode_FromFormat(self->line->to_string().c_str()); 46 | } 47 | 48 | /* Properties */ 49 | static PyGetSetDef Py_Line_getseters[] = { 50 | {"p1", (getter)Line_get_p1, NULL, "First point of the line.", NULL}, 51 | {"p2", (getter)Line_get_p2, NULL, "Second point of the line.", NULL}, 52 | {"m", (getter)Line_get_m, NULL, "Slope of the line.", NULL}, 53 | {"b", (getter)Line_get_b, NULL, "Y-intercept of the line.", NULL}, 54 | {NULL} /* Sentinel */ 55 | }; 56 | 57 | /* Line type definition */ 58 | static PyTypeObject Py_LineType = { 59 | PyVarObject_HEAD_INIT(NULL, 0) "Line", /* tp_name */ 60 | sizeof(Py_Line), /* tp_basicsize */ 61 | 0, /* tp_itemsize */ 62 | /* methods */ 63 | (destructor)Line_dealloc, /* tp_dealloc */ 64 | 0, /* tp_print */ 65 | 0, /* tp_getattr */ 66 | 0, /* tp_setattr */ 67 | 0, /* tp_reserved */ 68 | 0, /* tp_repr */ 69 | 0, /* tp_as_number */ 70 | 0, /* tp_as_sequence */ 71 | 0, /* tp_as_mapping */ 72 | 0, /* tp_hash */ 73 | 0, /* tp_call */ 74 | (reprfunc)Line_str, /* tp_str */ 75 | 0, /* tp_getattro */ 76 | 0, /* tp_setattro */ 77 | 0, /* tp_as_buffer */ 78 | Py_TPFLAGS_DEFAULT, /* tp_flags */ 79 | Py_Line_doc, /* tp_doc */ 80 | 0, /* tp_traverse */ 81 | 0, /* tp_clear */ 82 | 0, /* tp_richcompare */ 83 | 0, /* tp_weaklistoffset */ 84 | 0, /* tp_iter */ 85 | 0, /* tp_iternext */ 86 | Py_Line_methods, /* tp_methods */ 87 | 0, /* tp_members */ 88 | Py_Line_getseters, /* tp_getset */ 89 | 0, /* tp_base */ 90 | 0, /* tp_dict */ 91 | 0, /* tp_descr_get */ 92 | 0, /* tp_descr_set */ 93 | 0, /* tp_dictoffset */ 94 | (initproc)Line_init, /* tp_init */ 95 | 0, /* tp_alloc */ 96 | Line_new, /* tp_new */ 97 | }; 98 | 99 | static PyObject *Line_new(PyTypeObject *type, PyObject *args, PyObject *kwds) 100 | { 101 | Py_Line *self; 102 | self = (Py_Line *)type->tp_alloc(type, 0); 103 | return (PyObject *)self; 104 | } 105 | 106 | static int Line_init(Py_Line *self, PyObject *args, PyObject *kwds) 107 | { 108 | // arugments validation 109 | // check only either {p1, p2} or {m, b} in kwds 110 | if (kwds == NULL || PyDict_Size(kwds) != 2) 111 | { 112 | PyErr_SetString(PyExc_ValueError, "Invalid arguments. Use {p1, p2} or {m, b}.\n for example: Line(p1=(1,2), p2=(3,4)) or Line(m=1, b=2)"); 113 | return -1; 114 | } 115 | // if p1 and p2 exist in kwds 116 | if (PyDict_Contains(kwds, PyUnicode_FromString("p1")) && PyDict_Contains(kwds, PyUnicode_FromString("p2"))) 117 | { 118 | // get p1 and p2 119 | PyObject *p1_obj = PyDict_GetItem(kwds, PyUnicode_FromString("p1")); 120 | PyObject *p2_obj = PyDict_GetItem(kwds, PyUnicode_FromString("p2")); 121 | // // check if p1 and p2 are iterable 122 | // if (!PySequence_Check(p1_obj) || !PySequence_Check(p2_obj)) 123 | // { 124 | // PyErr_SetString(PyExc_ValueError, "Two points must be iterable."); 125 | // return -1; 126 | // } 127 | // // check if p1 and p2 are of length 2 128 | // if (PySequence_Length(p1_obj) != 2 || PySequence_Length(p2_obj) != 2) 129 | // { 130 | // PyErr_SetString(PyExc_ValueError, "Two points must be of length 2."); 131 | // return -1; 132 | // } 133 | 134 | // get p1 and p2 135 | double p1[2] = {PyFloat_AsDouble(PySequence_GetItem(p1_obj, 0)), PyFloat_AsDouble(PySequence_GetItem(p1_obj, 1))}; 136 | double p2[2] = {PyFloat_AsDouble(PySequence_GetItem(p2_obj, 0)), PyFloat_AsDouble(PySequence_GetItem(p2_obj, 1))}; 137 | // create line 138 | self->line = new Line(p1, p2); 139 | return 0; 140 | } 141 | // if m and b exist in kwds 142 | if (PyDict_Contains(kwds, PyUnicode_FromString("m")) && PyDict_Contains(kwds, PyUnicode_FromString("b"))) 143 | { 144 | // get m and b 145 | PyObject *m_obj = PyDict_GetItem(kwds, PyUnicode_FromString("m")); 146 | PyObject *b_obj = PyDict_GetItem(kwds, PyUnicode_FromString("b")); 147 | 148 | // get m and b 149 | double m = PyFloat_AsDouble(m_obj); 150 | double b = PyFloat_AsDouble(b_obj); 151 | // create line 152 | self->line = new Line(m, b); 153 | return 0; 154 | } 155 | // if neither p1 and p2 nor m and b exist in kwds 156 | PyErr_SetString(PyExc_ValueError, "Invalid arguments. Use {p1, p2} or {m, b}.\n for example: Line(p1=(1,2), p2=(3,4)) or Line(m=1, b=2)"); 157 | return -1; 158 | } 159 | 160 | static void Line_dealloc(Py_Line *self) 161 | { 162 | delete self->line; 163 | Py_TYPE(self)->tp_free((PyObject *)self); 164 | } 165 | 166 | static PyObject *Line_solve(Py_Line *self, PyObject *args, PyObject *kwds) 167 | { 168 | // arugments validation 169 | // check only either x(number) or y(number) in kwds 170 | if (kwds == NULL || PyDict_Size(kwds) != 1) 171 | { 172 | PyErr_SetString(PyExc_ValueError, "Invalid arguments. Use x(number) or y(number).\n for example: line.solve(x=1) or line.solve(y=1)"); 173 | return NULL; 174 | } 175 | // if x exist in kwds 176 | if (PyDict_Contains(kwds, PyUnicode_FromString("x"))) 177 | { 178 | // get x 179 | double x = PyFloat_AsDouble(PyDict_GetItem(kwds, PyUnicode_FromString("x"))); 180 | // solve 181 | return Py_BuildValue("d", self->line->solve(x)); 182 | } 183 | // if y exist in kwds 184 | if (PyDict_Contains(kwds, PyUnicode_FromString("y"))) 185 | { 186 | // get y 187 | double y = PyFloat_AsDouble(PyDict_GetItem(kwds, PyUnicode_FromString("y"))); 188 | // solve 189 | return Py_BuildValue("d", self->line->solve_x(y)); 190 | } 191 | // if neither x nor y exist in kwds 192 | PyErr_SetString(PyExc_ValueError, "Invalid arguments. Use x(number) or y(number).\n for example: line.solve(x=1) or line.solve(y=1)"); 193 | return NULL; 194 | } 195 | 196 | static PyObject *Line_crossed_by(Py_Line *self, PyObject *args) 197 | { 198 | // arguments validation 199 | // iterable[number, number] 200 | 201 | PyObject *p_obj = NULL; 202 | if (!PyArg_ParseTuple(args, "O", &p_obj)) 203 | { 204 | PyErr_SetString(PyExc_ValueError, "Invalid arguments. Use iterable[number, number]."); 205 | return NULL; 206 | } 207 | 208 | // get p 209 | double p[2] = {PyFloat_AsDouble(PySequence_GetItem(p_obj, 0)), PyFloat_AsDouble(PySequence_GetItem(p_obj, 1))}; 210 | return Py_BuildValue("i", self->line->crossed_by(p)); 211 | } 212 | 213 | static PyObject *Line_distance_to(Py_Line *self, PyObject *args) 214 | { 215 | // arguments validation 216 | // iterable[number, number] 217 | 218 | PyObject *p_obj = NULL; 219 | if (!PyArg_ParseTuple(args, "O", &p_obj)) 220 | { 221 | PyErr_SetString(PyExc_ValueError, "Invalid arguments. Use iterable[number, number]."); 222 | return NULL; 223 | } 224 | 225 | // get p 226 | double p[2] = {PyFloat_AsDouble(PySequence_GetItem(p_obj, 0)), PyFloat_AsDouble(PySequence_GetItem(p_obj, 1))}; 227 | return Py_BuildValue("d", self->line->distance_to(p)); 228 | } 229 | 230 | static PyObject *Line_intersection(Py_Line *self, PyObject *args) 231 | { 232 | // arguments validation 233 | // Py_Line 234 | 235 | Py_Line *line_obj = NULL; 236 | if (!PyArg_ParseTuple(args, "O!", &Py_LineType, &line_obj)) 237 | { 238 | PyErr_SetString(PyExc_ValueError, "Invalid arguments. Use Line."); 239 | return NULL; 240 | } 241 | double p[2]; 242 | if (self->line->intersection((*line_obj->line), p)) 243 | { 244 | return Py_BuildValue("(dd)", p[0], p[1]); 245 | } 246 | else 247 | { 248 | Py_RETURN_NONE; 249 | } 250 | } 251 | 252 | static PyObject *Line_get_points(Py_Line *self, void *closure) 253 | { 254 | double p1[2]; 255 | double p2[2]; 256 | self->line->get_points(p1, p2); 257 | return Py_BuildValue("((dd)(dd))", p1[0], p1[1], p2[0], p2[1]); 258 | } 259 | 260 | static PyObject *Line_get_p1(Py_Line *self, void *closure) 261 | { 262 | double p1[2]; 263 | self->line->get_p1(p1); 264 | return Py_BuildValue("(dd)", p1[0], p1[1]); 265 | } 266 | 267 | static PyObject *Line_get_p2(Py_Line *self, void *closure) 268 | { 269 | double p2[2]; 270 | self->line->get_p2(p2); 271 | return Py_BuildValue("(dd)", p2[0], p2[1]); 272 | } 273 | 274 | static PyObject *Line_get_m(Py_Line *self, void *closure) 275 | { 276 | return Py_BuildValue("d", self->line->get_m()); 277 | } 278 | 279 | static PyObject *Line_get_b(Py_Line *self, void *closure) 280 | { 281 | return Py_BuildValue("d", self->line->get_b()); 282 | } 283 | 284 | /* define module */ 285 | static struct PyModuleDef FastLine = { 286 | PyModuleDef_HEAD_INIT, 287 | "FastLine", /* name of module */ 288 | "FastLine provides line operations like intersection, distance, etc implemented in C++.", /* module documentation, may be NULL */ 289 | -1, /* size of per-interpreter state of the module, or -1 if the module keeps state in global variables. */ 290 | NULL}; 291 | 292 | PyMODINIT_FUNC PyInit_FastLine(void) 293 | { 294 | PyObject *m; 295 | 296 | m = PyModule_Create(&FastLine); 297 | if (m == NULL) 298 | return NULL; 299 | 300 | // PyReady 301 | if (PyType_Ready(&Py_LineType) < 0) 302 | return NULL; 303 | 304 | // add Line to module 305 | Py_INCREF(&Py_LineType); 306 | PyModule_AddObject(m, "Line", (PyObject *)&Py_LineType); 307 | 308 | return m; 309 | } -------------------------------------------------------------------------------- /src/Line.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class Line 5 | { 6 | public: 7 | Line(double p1[2], double p2[2]); 8 | Line(double m, double b); 9 | double solve(double x); 10 | double solve_x(double y); 11 | int crossed_by(double p[2]); 12 | double distance_to(double p[2]); 13 | bool intersection(Line l, double p[2]); 14 | private: 15 | double p1[2]; 16 | double p2[2]; 17 | double m; 18 | double b; 19 | // to prevent recalculation: 20 | double p2_sub_p1[2]; 21 | double p2_sub_p1_norm; 22 | // A, B, C coefficients of the line equation 23 | double A; 24 | double B; 25 | double C; 26 | public: 27 | void get_points(double p1[2], double p2[2]); 28 | double get_m(); 29 | double get_b(); 30 | void get_p1(double p1[2]); 31 | void get_p2(double p2[2]); 32 | std::string to_string(); 33 | }; 34 | 35 | Line::Line(double p1[2], double p2[2]) 36 | { 37 | this->p1[0] = p1[0]; 38 | this->p1[1] = p1[1]; 39 | this->p2[0] = p2[0]; 40 | this->p2[1] = p2[1]; 41 | this->m = (p2[1] - p1[1]) / (p2[0] - p1[0]); 42 | this->b = p1[1] - m * p1[0]; 43 | // to prevent recalculation: 44 | this->p2_sub_p1[0] = p2[0] - p1[0]; 45 | this->p2_sub_p1[1] = p2[1] - p1[1]; 46 | this->p2_sub_p1_norm = sqrt(p2_sub_p1[0] * p2_sub_p1[0] + p2_sub_p1[1] * p2_sub_p1[1]); 47 | // A, B, C coefficients of the line equation 48 | this->A = (p1[1] - p2[1]); 49 | this->B = (p2[0] - p1[0]); 50 | this->C = -(p1[0]*p2[1] - p2[0]*p1[1]); 51 | } 52 | 53 | Line::Line(double m, double b) 54 | { 55 | // produce two points on the line 56 | this->p1[0] = 0; 57 | this->p1[1] = b; 58 | this->p2[0] = 10; 59 | this->p2[1] = m * 10 + b; 60 | this->m = m; 61 | this->b = b; 62 | // to prevent recalculation: 63 | this->p2_sub_p1[0] = p2[0] - p1[0]; 64 | this->p2_sub_p1[1] = p2[1] - p1[1]; 65 | this->p2_sub_p1_norm = sqrt(p2_sub_p1[0] * p2_sub_p1[0] + p2_sub_p1[1] * p2_sub_p1[1]); 66 | // A, B, C coefficients of the line equation 67 | this->A = (p1[1] - p2[1]); 68 | this->B = (p2[0] - p1[0]); 69 | this->C = -(p1[0]*p2[1] - p2[0]*p1[1]); 70 | } 71 | 72 | double Line::solve(double x) 73 | { 74 | return m * x + b; 75 | } 76 | 77 | double Line::solve_x(double y) 78 | { 79 | return (y - b) / m; 80 | } 81 | 82 | int Line::crossed_by(double p[2]) 83 | { 84 | double v1[2] = {this->p2_sub_p1[0], this->p2_sub_p1[1]}; 85 | double v2[2] = {this->p2[0] - p[0], this->p2[1] - p[1]}; 86 | double xp = v1[0] * v2[1] - v1[1] * v2[0]; 87 | if (xp > 0) 88 | { 89 | return -1; 90 | } 91 | if (xp < 0) 92 | { 93 | return 1; 94 | } 95 | return 0; // on the line 96 | } 97 | 98 | double Line::distance_to(double p[2]) 99 | { 100 | double cross_product = fabs((p2_sub_p1[0] * (p1[1] - p[1])) - ((p1[0] - p[0]) * p2_sub_p1[1])); 101 | return cross_product / p2_sub_p1_norm; 102 | } 103 | 104 | bool Line::intersection(Line l, double p[2]) 105 | { 106 | double D = A * l.B - B * l.A; 107 | if (D == 0) 108 | { 109 | return false; 110 | } 111 | p[0] = (C * l.B - B * l.C) / D; 112 | p[1] = (A * l.C - C * l.A) / D; 113 | return true; 114 | } 115 | 116 | void Line::get_points(double p1[2], double p2[2]) 117 | { 118 | p1[0] = this->p1[0]; 119 | p1[1] = this->p1[1]; 120 | p2[0] = this->p2[0]; 121 | p2[1] = this->p2[1]; 122 | } 123 | 124 | double Line::get_m() 125 | { 126 | return this->m; 127 | } 128 | 129 | double Line::get_b() 130 | { 131 | return this->b; 132 | } 133 | 134 | void Line::get_p1(double p1[2]) 135 | { 136 | p1[0] = this->p1[0]; 137 | p1[1] = this->p1[1]; 138 | } 139 | 140 | void Line::get_p2(double p2[2]) 141 | { 142 | p2[0] = this->p2[0]; 143 | p2[1] = this->p2[1]; 144 | } 145 | 146 | std::string Line::to_string() 147 | { 148 | std::string s = "Line: "; 149 | s += std::to_string(this->p1[0]) + " " + std::to_string(this->p1[1]) + " " + std::to_string(this->p2[0]) + " " + std::to_string(this->p2[1]); 150 | s += " m: " + std::to_string(this->m) + " b: " + std::to_string(this->b); 151 | return s; 152 | } --------------------------------------------------------------------------------