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