{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "6f6fea74", "metadata": {}, "outputs": [], "source": [ "import shapely\n", "from shapely import LineString\n" ] }, { "cell_type": "markdown", "id": "beb9b257", "metadata": {}, "source": [] }, { "cell_type": "code", "execution_count": 2, "id": "7b3cdaa2", "metadata": {}, "outputs": [], "source": [ "line1 = LineString([(0, 0), (2, 2)])\n", "line2 = LineString([(1, 0), (1, 2)])\n" ] }, { "cell_type": "code", "execution_count": 3, "id": "58df4b55", "metadata": {}, "outputs": [], "source": [ "# shapely.intersection(line1, line2)" ] }, { "cell_type": "code", "execution_count": 4, "id": "f62e013a", "metadata": {}, "outputs": [], "source": [ "gap = shapely.intersection(line1.buffer(.1), line2)" ] }, { "cell_type": "code", "execution_count": 5, "id": "acbd14e8", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "" ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gapped = line2.difference(gap)\n", "gapped" ] }, { "cell_type": "code", "execution_count": 6, "id": "716105d1", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "" ], "text/plain": [ "" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "line1.union(gapped)" ] }, { "cell_type": "code", "execution_count": 77, "id": "366565dc", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 77, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pickle\n", "with open('/home/ruben/tmp/hof3-demo-renderablelines.pcl', 'rb') as fp:\n", " rl = pickle.load(fp)\n", "len(rl.lines)" ] }, { "cell_type": "code", "execution_count": 82, "id": "e97ccc33", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "" ], "text/plain": [ "" ] }, "execution_count": 82, "metadata": {}, "output_type": "execute_result" } ], "source": [ "line1 = LineString([p.position for p in rl.lines[0].points])\n", "line1_similar = LineString([[p.position[0]+.8, p.position[1]] for p in rl.lines[0].points])\n", "line1_similar2 = LineString([[p.position[0]+.2, p.position[1]+3] for p in rl.lines[0].points])\n", "\n", "line2 = LineString([p.position for p in rl.lines[1].points])\n", "\n", "all_lines = [line1, line1_similar, line1_similar2, line2]\n", "\n", "shapely.union_all(all_lines)\n" ] }, { "cell_type": "code", "execution_count": 81, "id": "694050df", "metadata": {}, "outputs": [], "source": [ "from typing import List\n", "\n", "\n", "def remove_overlaps(line_strings: List[LineString], boundary=.3):\n", " resulting_geometries: List[shapely.BaseGeometry] = []\n", " for line in line_strings:\n", " if not len(resulting_geometries):\n", " resulting_geometries.append(line)\n", " continue\n", "\n", " current_shape = shapely.union_all(resulting_geometries)\n", " result = line.difference(current_shape.buffer(boundary))\n", " # if result.is_empty: # keep empty, so that we keep track of index of the lines\n", " # continue\n", " resulting_geometries.append(result)\n", " return resulting_geometries\n", " " ] }, { "cell_type": "code", "execution_count": null, "id": "0cf807fe", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4 line\n" ] }, { "data": { "image/svg+xml": [ "" ], "text/plain": [ "" ] }, "execution_count": 83, "metadata": {}, "output_type": "execute_result" } ], "source": [ "clean_lines = remove_overlaps(all_lines, .3)\n", "print(len(clean_lines), \"line\")\n", "shapely.union_all(clean_lines)" ] }, { "cell_type": "code", "execution_count": 99, "id": "3b8fb8c9", "metadata": {}, "outputs": [], "source": [ "# line3d_1 = LineString([[p.position[0], p.position[1], p.color.alpha] for p in rl.lines[0].points])\n", "# line3d_2 = LineString([[p.position[0]+1, p.position[1], p.color.alpha] for p in rl.lines[0].points])\n", "# shapely.union_all([line3d_1, line3d_2])\n", "# shapely.difference(line3d_1, line3d_2.buffer(.3), axis=0) # axis does not exist" ] }, { "cell_type": "markdown", "id": "198ad339", "metadata": {}, "source": [ "# Self intersection\n", "This works different from intersection with another line. Thus, we would need to calculate this" ] }, { "cell_type": "code", "execution_count": 11, "id": "8bdeb8e4", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "" ], "text/plain": [ "" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "self_intersecting_line = LineString([[0,0], [2,2], [3,1], [1.5,0], [0,2], [3,0]])\n", "self_intersecting_line" ] }, { "cell_type": "code", "execution_count": 12, "id": "2f47a911", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(True, False)" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "line1.is_simple, self_intersecting_line.is_simple" ] }, { "cell_type": "code", "execution_count": 19, "id": "8298eb6a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "That trick does not work: Input geometry segment overlaps with the splitter.\n" ] } ], "source": [ "try:\n", " diff_line = shapely.ops.split(self_intersecting_line, self_intersecting_line)\n", "except Exception as e:\n", " print(\"That trick does not work:\", e)" ] }, { "cell_type": "code", "execution_count": 75, "id": "9fce60ac", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "" ], "text/plain": [ "" ] }, "execution_count": 75, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from shapely import MultiLineString, is_empty, is_simple\n", "\n", "def linestring_to_segments(ls: LineString) -> List[LineString]:\n", " return [\n", " LineString([self_intersecting_line.coords[i], self_intersecting_line.coords[i+1]]) \n", " for i in range(len(self_intersecting_line.coords)-1)\n", " ]\n", "\n", "def split_line_at_self_intersection(linestring: LineString, boundary = .3):\n", " if linestring.is_simple:\n", " return linestring\n", " segments = linestring_to_segments(linestring)\n", " new_segments = split_at_self_intersection(segments, boundary_size=boundary)\n", " if len(new_segments) == 1:\n", " return new_segments[0]\n", " return MultiLineString(new_segments) \n", "\n", "\n", "def split_at_self_intersection(segments, new_segments = [], boundary_size = .3):\n", " if not len(segments):\n", " return new_segments\n", " \n", " segment = segments.pop(0)\n", " for ns in new_segments[:-1]: # assume there's no overlap with last one\n", " if segment.intersects(ns):\n", " #cut\n", " parts = segment.difference(ns.buffer(boundary_size))\n", " if type(parts) is LineString:\n", " if not parts.is_empty:\n", " new_segments.append(parts)\n", " remaining_segments = segments\n", " elif type(parts) is MultiLineString:\n", " \n", " new_segments.append(parts.geoms[0]) # add the first part\n", " # prepend remaining bit, and calculate from there\n", " remaining_segments = [parts.geoms[1]]\n", " remaining_segments.extend(segments)\n", "\n", " return split_at_self_intersection(remaining_segments, new_segments, boundary_size)\n", " pass\n", " \n", " new_segments.append(segment)\n", " return split_at_self_intersection(segments, new_segments, boundary_size)\n", "\n", "# MultiLineString(split_at_self_intersection(segments, boundary_size=.1))\n", "split_line_at_self_intersection(self_intersecting_line, .3)" ] }, { "cell_type": "markdown", "id": "3fe70cb7", "metadata": {}, "source": [ "# Simplification" ] }, { "cell_type": "code", "execution_count": 61, "id": "0e82f549", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "27 6\n" ] }, { "data": { "image/svg+xml": [ "" ], "text/plain": [ "" ] }, "execution_count": 61, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print(line1.coords.__len__(), line1.simplify(.15).coords.__len__())\n", "line1.simplify(.15)" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "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.10.4" } }, "nbformat": 4, "nbformat_minor": 5 }