Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions .github/workflows/development.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
run: pip install flake8 && flake8

test:
name: Test ${{ matrix.platform }} Python ${{ matrix.python-version }}
name: Test on ${{ matrix.platform }} Python ${{ matrix.python-version }}
needs: lint
strategy:
max-parallel: 6
Expand All @@ -38,6 +38,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements_build.txt
pip install -r requirements_test.txt
- name: Build extensions
run: make build_ext
Expand All @@ -52,7 +53,31 @@ jobs:
git-branch: main
parallel: true

finish:
examples:
name: Run examples on Python ${{ matrix.python-version }}
needs: lint
strategy:
max-parallel: 6
matrix:
python-version: [ "3.10", "3.11"]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install package
run: make install-mpl
- name: Test examples
run: |
make install;
for i in examples/*.py; do
python $i && echo "$i is ok" || echo "$i fails";
done;

finish-coverage:
name: Finalizing
needs: test
runs-on: ubuntu-latest
Expand All @@ -61,4 +86,4 @@ jobs:
uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.github_token }}
parallel-finished: true
parallel-finished: true
32 changes: 18 additions & 14 deletions examples/building_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
Usage example showing how to map hexagons with different properties,
calculate path and rendering result with a debug front end.
"""
import matplotlib.pyplot as plt

import geks
from geks.front import FrontMPL


# Create rectangular map of unblocked hexagons with pointy top
default_value = {"price": 1, "blocked": False}
hm = geks.RectHexmap(default_value, dims=(12, 8), flat=True)
Expand All @@ -22,34 +23,37 @@
# Set rugged terrain by drawing a circle
swamps = geks.hex_circle(geks.Hex((3, 0)), 1)
for sw in swamps:
if sw in hm.map:
hm.map[sw]["price"] = 5
if sw in hm:
hm[sw]["price"] = 5

# Draw hexagons according to their values
for he, value in hm.map.items():
for he, value in hm.items():
mpl.plot_hex(
he,
fill=value["blocked"] or value["price"] > 1,
fillcolor="grey" if value["blocked"] else "lightgreen",
edgecolor="lightgrey",
facecolor="grey" if value["blocked"] else "lightgreen",
edgecolor="black",
)

# Highlight start and target hexagons
start = geks.Hex((0, 0))
target = geks.Hex((11, 2))
mpl.plot_hex(start, fill=True, fillcolor="lightblue")
mpl.plot_hex(target, fill=True, fillcolor="orange")
mpl.plot_hex(start, facecolor="lightblue")
mpl.plot_hex(target, facecolor="orange")

# Build path
path, nstep, nit = geks.dijkstra_path(
path, nstep = geks.dijkstra_path(
hm,
start,
target,
distance=40,
block_func=lambda y, x: hm.map[x]["blocked"], # walls condition
cost_func=lambda y, x: hm.map[x]["price"], # cost of movement
block_func=lambda y, x: hm[x]["blocked"], # walls condition
cost_func=lambda y, x: hm[x]["price"], # cost of movement
)
mpl.plot_path(path, color="darkblue")
mpl.plot_path(path, lw=2, color="darkblue")

# Show results
mpl.show()
# Save resulting image
mpl.fig.set_size_inches(10, 8)
plt.gca().invert_yaxis()
plt.autoscale(enable=True)
plt.savefig("example.png")
12 changes: 9 additions & 3 deletions examples/heights_and_rivers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import numpy as np
import matplotlib.pyplot as plt

import geks
from geks.front import FrontMPL
import numpy as np

# Prepare height map
hm = geks.RoundHexmap(0, 32)
Expand All @@ -24,6 +26,10 @@

# Generate 10 rivers
for edges in geks.generate_rivers(hm, 10, ocean_alt, source_min_alt):
mpl.plot_border(edges, color="#1F3C41", width=1.2)
mpl.plot_border(edges, color="#1F3C41", lw=1.2)

mpl.show()
# Save resulting image
mpl.fig.set_size_inches(16, 16)
plt.gca().invert_yaxis()
plt.autoscale(enable=True)
plt.savefig("example.png", transparent=True)
12 changes: 5 additions & 7 deletions geks/__init__.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
from .hexagon import Hex
from .edge import HexEdge
from .hexmap import RoundHexmap, RectHexmap
from .layout import Layout
from .pathfinding import dijkstra_scan, dijkstra_path
from .heightmap import (
gen_heightmap,
fill_sinks_py,
fill_sinks_cy,
altitude_cdf,
)
from .heightmap import gen_heightmap, fill_sinks_cy, fill_sinks_py, \
altitude_cdf
from .rivers import generate_rivers
from .geometry import hex_distance, hex_line, hex_ring, hex_circle, hex_rotate

__all__ = [
"Hex",
"HexEdge",
"RoundHexmap",
"RectHexmap",
"Layout",
"dijkstra_scan",
"dijkstra_path",
"gen_heightmap",
"fill_sinks_py",
"fill_sinks_cy",
"fill_sinks_py",
"altitude_cdf",
"generate_rivers",
"hex_distance",
Expand Down
49 changes: 21 additions & 28 deletions geks/front/front_mpl.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def __init__(self, layout):
self.layout = layout
self.fig, self.ax = plt.subplots(1)
self.ax.set_aspect("equal")
self.ax.set(frame_on=False)

self.ax.set_yticklabels([])
self.ax.set_xticklabels([])
Expand All @@ -27,61 +28,53 @@ def __init__(self, layout):
lambda x: print(self.layout.pixel2hex((x.xdata, x.ydata)).round()),
)

def show(self):
"""Shows rendered objects with selected matplotlib backend"""
plt.gca().invert_yaxis()
plt.autoscale(enable=True)
plt.show()

def plot_hex(
self, he, fill=False, fillcolor=None, edgecolor=None, alpha=1
):
def plot_hex(self, he, **kwargs):
"""Renders hexagon"""
patch = Polygon(
self.layout.hex_corners(he),
fill=fill,
facecolor=fillcolor,
edgecolor=edgecolor,
alpha=alpha,
)
patch = Polygon(self.layout.hex_corners(he), **kwargs)
self.ax.add_patch(patch)

def plot_hexmap(self, hm, colors, ignore_func=lambda x: False):
def plot_hexmap(self, hm, colors, ignore_func=lambda x: False, **kwargs):
"""Renders collection of hexagons"""
cmap = LinearSegmentedColormap.from_list("terrain", colors)

patches = []
for he, val in hm.items():
if ignore_func(he):
continue
patch = Polygon(self.layout.hex_corners(he), color=cmap(val))
patch = Polygon(
self.layout.hex_corners(he),
color=cmap(val),
**kwargs
)
patches.append(patch)
pc = PatchCollection(patches, match_original=True)
self.ax.add_collection(pc)

def plot_path(self, path, width=1, color=None):
def plot_path(self, path, **kwargs):
"""Renders a trace connecting given hexagons"""
if not path or len(path) < 2:
return
xy = np.array([self.layout.hex2pixel(he) for he in path])
self.ax.plot(xy[:, 0], xy[:, 1], linewidth=width, color=color)
self.ax.plot(xy[:, 0], xy[:, 1], **kwargs)

def plot_edge(self, he, di, width=1, color=None):
def plot_edge(self, he, di, **kwargs):
"""Renders hexagon's edge in given direction"""
edge = self.layout.hex_edge(he, di)
self.ax.plot(edge[:, 0], edge[:, 1], color=color, linewidth=width)
self.ax.plot(edge[:, 0], edge[:, 1], **kwargs)

def plot_border(self, edges, width=1, color=None):
def plot_border(self, edges, **kwargs):
if len(edges) == 0:
return
xy = np.array([self.layout.hex_edge(e) for e in edges])
xy = xy.reshape(-1, xy.shape[-1])
xy = np.delete(xy, np.arange(1, xy.shape[0] - 1, 2), axis=0)
self.ax.plot(xy[:, 0], xy[:, 1], linewidth=width, color=color)
self.ax.plot(xy[:, 0], xy[:, 1], **kwargs)

def label(self, he, text, color=None):
def label(self, he, text, **kwargs):
"""Renders text inside hexagon"""
pos = self.layout.hex2pixel(he)
self.ax.annotate(
text, pos, ha="center", va="center", color=color
)
if "ha" not in kwargs:
kwargs["ha"] = "center"
if "va" not in kwargs:
kwargs["va"] = "center"
self.ax.annotate(text, pos, **kwargs)
2 changes: 1 addition & 1 deletion geks/hexmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def center(self):
"""Returns central hexagon."""
center = self.dims // 2
if self.flat:
pass
center[1] -= center[0] // 2
else:
center[0] -= center[1] // 2
return Hex(center)
8 changes: 1 addition & 7 deletions geks/rivers.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ def generate_rivers(
ocean_alt,
source_min_alt,
source_min_dist=2,
edgify=True,
curv=None,
curv=None
):
"""
Generates rivers from the given minimal altitude to the ocean level or the
Expand All @@ -96,8 +95,6 @@ def generate_rivers(
Minimal altitude of river sources
source_min_dist : int, default: 2
Minimal distance between river sources
edgify : bool, default: True
Plot rivers through hexagons' edges
curv : int or None, default: None
If None, edgified river curvature increases as river gets closer to
the ocean. Otherwise curvature will be fixed, the higher the value is.
Expand All @@ -120,9 +117,6 @@ def generate_rivers(
pool = [he for he in pool if he not in busy]
it += 1

if not edgify:
return rivers

# Direct rivers through edges of hexagons and handle mergers
all_edges = []
for river in rivers:
Expand Down
2 changes: 0 additions & 2 deletions requirements_test.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
Cython>=3.0.2
numpy>=1.23.5
pytest>=7.4.0
pytest-cov>=4.1.0
11 changes: 11 additions & 0 deletions test/test_borders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import geks


def test_edge_equal():
he = geks.Hex((0, 0))
di = geks.Hex((1, 1))
edge0 = geks.HexEdge(he, di, 1)

ne = he + di
edge1 = geks.HexEdge(ne, he - ne, 1)
assert edge0 == edge1
9 changes: 1 addition & 8 deletions test/test_rivers.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import geks


def test_river_centers():
hm = geks.RectHexmap(None, (40, 20))
geks.gen_heightmap(hm)
geks.fill_sinks_cy(hm)
geks.generate_rivers(hm, 5, -1, 150, edgify=False)


def test_river_edges():
def test_river():
hm = geks.RectHexmap(None, (40, 20))
geks.gen_heightmap(hm)
geks.fill_sinks_cy(hm)
Expand Down