{ "cells": [ { "cell_type": "markdown", "id": "98cee297-1d0c-4238-83cb-2eca86431fde", "metadata": {}, "source": [ "# Polygon validation\n", "\n", "Making the polygons be non-self-intersecting is critical. There are routines in GEOS (and exposed in teqpflsh) to break up self-intersecting polygons into non-self-intersecting polygons" ] }, { "cell_type": "code", "execution_count": null, "id": "6b45a70c", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "import numpy as np\n", "import teqpflsh\n", "import matplotlib.pyplot as plt\n", "teqpflsh.__version__" ] }, { "cell_type": "code", "execution_count": null, "id": "b6182393", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "ptr = teqpflsh.GeometryFactoryHolder()\n", "\n", "# A bowtie curve that is periodic and self-intersecting\n", "t = np.linspace(0+0.1, 2*np.pi+0.1, 10000)\n", "X,Y = np.cos(t), np.cos(t)*np.sin(t)\n", "poly1 = ptr.makeclosedpolygon(X, Y)\n", "plt.plot(X, Y)\n", "plt.plot(X[0], Y[0], 'o')\n", "poly1.isValid # False since self-intersecting" ] }, { "cell_type": "markdown", "id": "ca4134dc-7c34-4894-a289-f51717086c14", "metadata": {}, "source": [ "The reason this matters in this context is that we need to be able to sample the domain randomly to generate guess values." ] }, { "cell_type": "code", "execution_count": null, "id": "82c3f307-8322-4fa8-be80-f245ae866360", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [ "raises-exception" ] }, "outputs": [], "source": [ "# This doesn't work because the triangulation fails because the geometry is not valid\n", "N = 10000\n", "x = np.zeros((N, ))\n", "y = np.zeros((N, ))\n", "teqpflsh.sample_random(poly1, 10000, x, y)" ] }, { "cell_type": "markdown", "id": "a8e9c1d5-3aa6-44b3-a766-9c413395e1c5", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Now we need to break up the polygon into portions that are simple (non self-intersecting) with the MakeValid class of geos : https://libgeos.org/doxygen/classgeos_1_1operation_1_1valid_1_1MakeValid.html" ] }, { "cell_type": "code", "execution_count": null, "id": "038ff472-6551-47c6-bb33-151f1ff4e483", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "simpl = poly1.make_valid()\n", "print(f'N: {simpl.getNumGeometries()}')\n", "print(f'simple: {simpl.isSimple}')\n", "print(f'valid: {simpl.isValid}')\n", "for i in range(simpl.getNumGeometries()):\n", " pI = simpl.getGeometryN(i)\n", " plt.plot(*pI.getXY())\n", " print(f'N: {pI.getNumGeometries()}')\n", " print(f'simple: {pI.isSimple}')\n", " print(f'valid: {pI.isValid}')" ] }, { "cell_type": "code", "execution_count": null, "id": "30b47157-b3c5-4090-a4f4-10a642661cbb", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "# And now sampling works after forcing validity\n", "N = 10000\n", "x = np.zeros((N, ))\n", "y = np.zeros((N, ))\n", "teqpflsh.sample_random(simpl, 10000, x, y)\n", "plt.plot(x, y, '.', ms=1);" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.8" } }, "nbformat": 4, "nbformat_minor": 5 }