{
 "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
}