diff --git a/.flake8 b/.flake8
index 8823017..1058019 100644
--- a/.flake8
+++ b/.flake8
@@ -1,5 +1,5 @@
[flake8]
-ignore = E203, W503
+ignore = E203, W503, F405, F403
# line length is intentionally set to 80 here because black uses Bugbear
# See https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#line-length for more details
-max-line-length = 95
\ No newline at end of file
+max-line-length = 100
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..9f0e915
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,20 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is and what you expected to happen.
+
+**Used bbox parameter**
+Please provide your input parameters so we can reproduce the issue.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/README.md b/README.md
index 0ad9558..6ee3680 100644
--- a/README.md
+++ b/README.md
@@ -15,29 +15,31 @@ This open source project generates any chosen location from the real world in Mi
The raw data obtained from the API *[(see FAQ)](#question-faq)* includes each element (buildings, walls, fountains, farmlands, etc.) with its respective corner coordinates (nodes) and descriptive tags. When you run the script, the following steps are performed automatically to generate a Minecraft world:
+#### Processing Pipeline
1. Scraping Data from API: The script fetches geospatial data from the Overpass Turbo API.
2. Determine Coordinate Extremes: Identifies the highest and lowest latitude and longitude values from the dataset.
3. Standardize Coordinate Lengths: Ensures all coordinates are of uniform length and removes the decimal separator.
4. Normalize Data: Adjusts all coordinates to start from zero by subtracting the previously determined lowest values.
5. Parse Data: Transforms the raw data into a standardized structure.
-6. Assign IDs to Elements: Iterates through each element and assigns a corresponding ID *[(see FAQ)](#question-faq)* at each coordinate in an array.
+6. Sort elements by priority: Enables a layering system with prioritized elements.
7. Optimize Array Size: Focuses on the outermost buildings to reduce array size.
-8. Integrate Landuse and Additional Data: Combines the landuse layer with other generated data.
-9. Generate Minecraft World: Iterates through the array to create the Minecraft world, including 3D structures like forests, houses, and cemetery graves.
+8. Generate Minecraft World: Iterates through the array to create the Minecraft world, including 3D structures like forests, houses, and rivers.
## :keyboard: Usage
```python3 arnis.py --bbox="min_lng,min_lat,max_lng,max_lat" --path="C:/Users/username/AppData/Roaming/.minecraft/saves/worldname"```
-Use http://bboxfinder.com/ to draw a rectangle of your wanted area. Then copy the four box coordinates as shown below and use them as the input for the --bbox parameter.
+Use http://bboxfinder.com/ to draw a rectangle of your wanted area. Then copy the four box coordinates as shown below and use them as the input for the --bbox parameter.
![How to find area](https://github.com/louis-e/arnis/blob/main/gitassets/bbox-finder.png?raw=true)
+The world will always be generated starting from the coordinates 0 0 0.
Manually generate a new Minecraft world (preferably a flat world) before running the script.
The --bbox parameter specifies the bounding box coordinates in the format: min_lng,min_lat,max_lng,max_lat.
Use --path to specify the location of the Minecraft world.
+With the --timeout parameter you can set the timeout for the floodfill algorithm in seconds (default: 2).
You can optionally use the parameter --debug to see processed value outputs during runtime.
#### Experimental City/State/Country Input Method
-The following method is experimental and may not perform as expected:
+The following method is experimental and may not perform as expected. Support is limited.
```python3 arnis.py --city="CityName" --state="StateName" --country="CountryName" --path="C:/Users/username/AppData/Roaming/.minecraft/saves/worldname"```
@@ -62,55 +64,35 @@ docker cp CONTAINER_ID:/home/region DESTINATION_PATH
## :question: FAQ
- *Why do some cities take so long to generate?*
-The script's performance can be significantly affected by large elements, such as extensive farmlands. The current floodfill algorithm can slow down considerably when dealing with such elements, leading to long processing times that can stretch to hours for large cities. Thus there is also a timeout restriction in place. It is recommended to start with smaller towns to get a sense of the script's performance. Future improvements will focus on making the floodfill algorithm multi-threaded to utilize multiple CPU cores and reduce processing times drastically.
+The script's performance can be significantly affected by large elements, such as extensive farmlands. The floodfill algorithm can slow down considerably when dealing with such elements, leading to long processing times. Thus there is also a timeout restriction in place, which can be adjusted by the user *[(see Usage)](#keyboard-usage)*. It is recommended to start with smaller areas to get a sense of the script's performance. Continuous improvements on the algorithm especially focus on effiency improvements.
- *Where does the data come from?*
The geographic data is sourced from OpenStreetMap (OSM)[^1], a free, collaborative mapping project that serves as an open-source alternative to commercial mapping services. The data is accessed via the Overpass API, which queries OSM's database.
- *How does the Minecraft world generation work?*
The script uses the [anvil-parser](https://github.com/matcool/anvil-parser) library to interact with Minecraft's world format. This library allows the script to create and manipulate Minecraft region files, enabling the generation of real-world locations within the game.
- *Where does the name come from?*
The project is named after Arnis[^2], the smallest city in Germany. The city's small size made it an ideal test case for developing and debugging the script efficiently.
-- *What are the corresponding IDs?*
-During the processing stage (*[(see How it works)](#floppy_disk-how-it-works)*), each element is assigned an ID that determines how it will be represented in Minecraft. This system ensures that different layers (e.g., buildings, roads, landuse) are rendered correctly without conflicts.
-
-ID | Name | Note |
---- | --- | --- |
-0 | Ground | |
-10 | Street | |
-11 | Footway | |
-12 | Natural path | |
-13 | Bridge | |
-14 | Railway | |
-19 | Street markings | Work in progress *[(see FAQ)](#question-faq)* |
-20 | Parking | |
-21 | Fountain border | |
-22 | Fence | |
-30 | Meadow | |
-31 | Farmland | |
-32 | Forest | |
-33 | Cemetery | |
-34 | Beach | |
-35 | Wetland | |
-36 | Pitch | |
-37 | Swimming pool | |
-38 | Water | |
-39 | Raw grass | |
-50-59 | House corner | The last digit refers to the building height |
-60-69 | House wall | The last digit refers to the building height |
-70-79 | House interior | The last digit refers to the building height |
## :memo: ToDo
Feel free to choose an item from the To-Do or Known Bugs list, or bring your own idea to the table. Contributions from everyone are welcome and encouraged to help improve this project.
-- [x] Alternative reliable city input options[^3]
-- [ ] Split up processData array into several smaller ones for big cities[^4]
-- [ ] Floodfill timeout parameters
-- [ ] Implement multiprocessing in floodfill algorithm in order to boost CPU bound calculation performance
-- [ ] Generate a few big cities using high performance hardware and make them available to download[^3]
-- [ ] Add code comments
+- [ ] Look into https://github.com/Intergalactyc/anvil-new which seems to have a better support
+- [ ] Tool for mapping real coordinates to Minecraft coordinates
+- [ ] Fix railway orientation
+- [ ] Fix gaps in bridges
+- [ ] Full refactoring of variable and function names, establish naming conventions
+- [ ] Detection of wrong bbox input
+- [ ] Evaluate and implement multiprocessing in the ground layer initialization and floodfill algorithm
- [ ] Implement elevation
-- [ ] Find alternative for CV2 package
- [ ] Add interior to buildings
+- [ ] Save fountain structure in the code (similar to the tree structure)
+- [ ] Add windows to buildings
+- [ ] Generate a few big cities using high performance hardware and make them available to download
- [ ] Optimize region file size
- [ ] Street markings
+- [ ] Add better code comments
+- [x] Alternative reliable city input options
+- [x] Split up processData array into several smaller ones for big cities
+- [x] Find alternative for CV2 package
+- [x] Floodfill timeout parameter
- [x] Automated Tests
- [x] PEP8
- [x] Use f-Strings in print statements
@@ -119,10 +101,10 @@ Feel free to choose an item from the To-Do or Known Bugs list, or bring your own
- [x] Improve RAM usage
## :bug: Known Bugs
-- [ ] 'Noer' bug (occurs when several different digits appear in coordinates before the decimal point)
-- [x] 'Nortorf' bug (occurs when there are several elements with a big distance to each other, e.g. the API returns several different cities with the exact same name)
-- [ ] Saving step memory overflow
- [ ] Docker image size
+- [x] 'Noer' bug (occurs when several different digits appear in coordinates before the decimal point)
+- [x] 'Nortorf' bug (occurs when there are several elements with a big distance to each other, e.g. the API returns several different cities with the exact same name)
+- [x] Saving step memory overflow
- [x] Non uniform OSM naming standards (dashes) (See name tags at https://overpass-turbo.eu/s/1mMj)
## :trophy: Hall of Fame Contributors
@@ -139,7 +121,7 @@ This section is dedicated to recognizing and celebrating the outstanding contrib
[![Star History Chart](https://api.star-history.com/svg?repos=louis-e/arnis&type=Date)](https://star-history.com/#louis-e/arnis&Date)
## :copyright: License
-MIT License[^5]
+MIT License[^3]
Copyright (c) 2022 louis-e
@@ -147,11 +129,7 @@ Copyright (c) 2022 louis-e
[^2]: https://en.wikipedia.org/wiki/Arnis,_Germany
-[^3]: This might require a complete redesign of the algorithm since the current version is based on the city / state / country input. This will be investigated further soon.
-
-[^4]: https://github.com/louis-e/arnis/issues/21#issuecomment-1785801652
-
-[^5]:
+[^3]:
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice, the author ("louis-e") and this permission notice shall be included in all copies or substantial portions of the Software.
diff --git a/requirements.txt b/requirements.txt
index 4493813..b48c3eb 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,6 @@
anvil-parser==0.9.0
matplotlib==3.9.0
numpy==1.26.4
-opencv-python==4.9.0.80
pytest==8.2.1
python-polylabel==0.6
requests==2.32.2
diff --git a/src/blockDefinitions.py b/src/blockDefinitions.py
new file mode 100644
index 0000000..4b9ed98
--- /dev/null
+++ b/src/blockDefinitions.py
@@ -0,0 +1,95 @@
+import anvil
+
+air = anvil.Block("minecraft", "air")
+birch_leaves = anvil.Block("minecraft", "birch_leaves")
+birch_log = anvil.Block("minecraft", "birch_log")
+black_concrete = anvil.Block("minecraft", "black_concrete")
+blue_flower = anvil.Block("minecraft", "blue_orchid")
+brick = anvil.Block("minecraft", "bricks")
+carrots = anvil.Block("minecraft", "carrots", {"age": 7})
+cauldron = anvil.Block.from_numeric_id(118, 0)
+cobblestone = anvil.Block("minecraft", "cobblestone")
+cobblestone_wall = anvil.Block("minecraft", "cobblestone_wall")
+dark_oak_door_lower = anvil.Block("minecraft", "dark_oak_door", {"half": "lower"})
+dark_oak_door_upper = anvil.Block("minecraft", "dark_oak_door", {"half": "upper"})
+dirt = anvil.Block("minecraft", "dirt")
+farmland = anvil.Block("minecraft", "farmland")
+glass = anvil.Block("minecraft", "glass_pane")
+glowstone = anvil.Block("minecraft", "glowstone")
+grass = anvil.Block.from_numeric_id(175, 2)
+grass_block = anvil.Block("minecraft", "grass_block")
+gravel = anvil.Block("minecraft", "gravel")
+gray_concrete = anvil.Block("minecraft", "gray_concrete")
+green_stained_hardened_clay = anvil.Block.from_numeric_id(159, 5)
+hay_bale = anvil.Block("minecraft", "hay_block")
+iron_block = anvil.Block("minecraft", "iron_block")
+light_gray_concrete = anvil.Block("minecraft", "light_gray_concrete")
+oak_fence = anvil.Block.from_numeric_id(85, 0)
+oak_leaves = anvil.Block("minecraft", "oak_leaves")
+oak_log = anvil.Block.from_numeric_id(17)
+oak_planks = anvil.Block("minecraft", "oak_planks")
+podzol = anvil.Block.from_numeric_id(3, 2)
+potatoes = anvil.Block("minecraft", "potatoes", {"age": 7})
+rail = anvil.Block("minecraft", "rail")
+red_flower = anvil.Block.from_numeric_id(38)
+sand = anvil.Block("minecraft", "sand")
+scaffolding = anvil.Block("minecraft", "scaffolding")
+sponge = anvil.Block("minecraft", "sponge")
+spruce_log = anvil.Block("minecraft", "spruce_log")
+stone = anvil.Block("minecraft", "stone")
+stone_block_slab = anvil.Block.from_numeric_id(44, 0)
+stone_brick_slab = anvil.Block.from_numeric_id(44, 5)
+water = anvil.Block("minecraft", "water")
+wheat = anvil.Block("minecraft", "wheat", {"age": 7})
+white_concrete = anvil.Block("minecraft", "white_concrete")
+white_flower = anvil.Block("minecraft", "azure_bluet")
+white_stained_glass = anvil.Block("minecraft", "white_stained_glass")
+yellow_flower = anvil.Block("minecraft", "dandelion")
+
+# Variations for building corners
+building_corner_variations = [
+ anvil.Block("minecraft", "stone_bricks"),
+ anvil.Block("minecraft", "cobblestone"),
+ anvil.Block("minecraft", "bricks"),
+ anvil.Block("minecraft", "mossy_cobblestone"),
+ anvil.Block("minecraft", "sandstone"),
+ anvil.Block("minecraft", "red_nether_bricks"),
+ anvil.Block("minecraft", "blackstone"),
+ anvil.Block("minecraft", "smooth_quartz"),
+ anvil.Block("minecraft", "chiseled_stone_bricks"),
+ anvil.Block("minecraft", "polished_basalt"),
+ anvil.Block("minecraft", "cut_sandstone"),
+ anvil.Block("minecraft", "polished_blackstone_bricks"),
+]
+
+# Variations for building walls
+building_wall_variations = [
+ anvil.Block("minecraft", "white_terracotta"),
+ anvil.Block("minecraft", "gray_terracotta"),
+ anvil.Block("minecraft", "bricks"),
+ anvil.Block("minecraft", "smooth_sandstone"),
+ anvil.Block("minecraft", "red_terracotta"),
+ anvil.Block("minecraft", "polished_diorite"),
+ anvil.Block("minecraft", "smooth_stone"),
+ anvil.Block("minecraft", "polished_andesite"),
+ anvil.Block("minecraft", "warped_planks"),
+ anvil.Block("minecraft", "end_stone_bricks"),
+ anvil.Block("minecraft", "smooth_red_sandstone"),
+ anvil.Block("minecraft", "nether_bricks"),
+]
+
+# Variations for building floors
+building_floor_variations = [
+ anvil.Block("minecraft", "oak_planks"),
+ anvil.Block("minecraft", "spruce_planks"),
+ anvil.Block("minecraft", "dark_oak_planks"),
+ anvil.Block("minecraft", "stone_bricks"),
+ anvil.Block("minecraft", "polished_granite"),
+ anvil.Block("minecraft", "polished_diorite"),
+ anvil.Block("minecraft", "acacia_planks"),
+ anvil.Block("minecraft", "jungle_planks"),
+ anvil.Block("minecraft", "warped_planks"),
+ anvil.Block("minecraft", "purpur_block"),
+ anvil.Block("minecraft", "smooth_red_sandstone"),
+ anvil.Block("minecraft", "polished_blackstone"),
+]
diff --git a/src/floodFill.py b/src/floodFill.py
deleted file mode 100644
index f130f99..0000000
--- a/src/floodFill.py
+++ /dev/null
@@ -1,108 +0,0 @@
-from time import time
-import numpy as np
-import matplotlib.path as mplPath
-from polylabel import polylabel
-
-
-def floodFill(
- img, px, py, newColor, currentBuilding, minMaxDistX, minMaxDistY, elementType="None"
-):
- startTimeFloodfill = time()
- currentBuilding = np.delete(currentBuilding, 0, axis=0)
- if len(currentBuilding) <= 2 or not (px < minMaxDistY and py < minMaxDistX):
- return img
- if not (mplPath.Path(currentBuilding).contains_point((py, px))):
- centroid = polylabel([currentBuilding.tolist()], with_distance=True)
- px = round(centroid[0][1])
- py = round(centroid[0][0])
- if not (mplPath.Path(currentBuilding).contains_point((py, px))):
- if mplPath.Path(currentBuilding).contains_point((py - 5, px)):
- py -= 5
- elif mplPath.Path(currentBuilding).contains_point((py + 5, px)):
- py += 5
- elif mplPath.Path(currentBuilding).contains_point((py, px - 5)):
- px -= 5
- elif mplPath.Path(currentBuilding).contains_point((py, px + 5)):
- px += 5
- else:
- return img
-
- if str(img[px][py][0])[:1] == "5" or str(img[px][py][0])[:1] == "6":
- if mplPath.Path(currentBuilding).contains_point((py - 1, px)):
- py -= 1
- elif mplPath.Path(currentBuilding).contains_point((py + 1, px)):
- py += 1
- elif mplPath.Path(currentBuilding).contains_point((py, px - 1)):
- px -= 1
- elif mplPath.Path(currentBuilding).contains_point((py, px + 1)):
- px += 1
- else:
- return img
-
- try:
- oldColor = img[px][py][0]
- except Exception:
- return img
- queue = [(px, py)]
- seen = set()
- tot_rows = img.shape[0]
- tot_cols = img.shape[1]
- queueLen = 0
- while queue:
- nxt = []
- for x, y in queue:
- if img[x][y] == newColor:
- continue
- if not (mplPath.Path(currentBuilding).contains_point((y, x))):
- return img
- img[x][y] = newColor
- seen.add((x, y))
-
- if (
- x
- and (x - 1, y) not in seen
- and (
- img[x - 1][y] == oldColor
- or (elementType == "building" and str(img[x - 1][y][0])[:1] == "1")
- )
- ):
- nxt.append((x - 1, y))
- if (
- y
- and (x, y - 1) not in seen
- and (
- img[x][y - 1] == oldColor
- or (elementType == "building" and str(img[x][y - 1][0])[:1] == "1")
- )
- ):
- nxt.append((x, y - 1))
- if (
- x < tot_rows - 1
- and (x + 1, y) not in seen
- and (
- img[x + 1][y] == oldColor
- or (elementType == "building" and str(img[x + 1][y][0])[:1] == "1")
- )
- ):
- nxt.append((x + 1, y))
- if (
- y < tot_cols - 1
- and (x, y + 1) not in seen
- and (
- img[x][y + 1] == oldColor
- or (elementType == "building" and str(img[x][y + 1][0])[:1] == "1")
- )
- ):
- nxt.append((x, y + 1))
-
- # Timeout (known issue, see Github readme)
- if time() - startTimeFloodfill > 7 or (
- elementType == "tree_row" and time() - startTimeFloodfill > 0.2
- ):
- return img
-
- queue = nxt
- if len(nxt) > queueLen:
- queueLen = len(nxt)
-
- return img
diff --git a/src/getData.py b/src/getData.py
index 60b2b54..de07d31 100644
--- a/src/getData.py
+++ b/src/getData.py
@@ -51,7 +51,7 @@ def getData(city, state, country, bbox, file, debug, download_method="requests")
]
url = choice(api_servers)
- if state:
+ if city:
query1 = f"""
[out:json];
area[name="{city}"]->.city;
@@ -91,6 +91,8 @@ def getData(city, state, country, bbox, file, debug, download_method="requests")
nwr["bridge"];
nwr["railway"];
nwr["barrier"];
+ nwr["entrance"];
+ nwr["door"];
)->.waysinbbox;
(
node(w.waysinbbox);
diff --git a/src/main.py b/src/main.py
index dcd4112..0e15a0d 100644
--- a/src/main.py
+++ b/src/main.py
@@ -4,19 +4,14 @@
# MIT License
# Please see the LICENSE file that should have been included as part of this package.
-from math import floor
-from random import randint
-from tqdm import tqdm
-import anvil
import argparse
import gc
import numpy as np
import os
import sys
-import time
from .getData import getData
-from .processData import processData
+from .processData import processRawData, generateWorld
parser = argparse.ArgumentParser(
description="Arnis - Generate cities from real life in Minecraft"
@@ -45,6 +40,13 @@
action="store_true",
help="Enable debug mode",
)
+parser.add_argument(
+ "--timeout",
+ dest="timeout",
+ default=2,
+ action="store_true",
+ help="Set floodfill timeout (seconds)",
+)
args = parser.parse_args()
# Ensure either bbox or city/state/country is provided
@@ -64,96 +66,11 @@
np.seterr(all="raise")
np.set_printoptions(threshold=sys.maxsize)
-processStartTime = time.time()
-air = anvil.Block("minecraft", "air")
-glass = anvil.Block("minecraft", "glass_pane")
-brick = anvil.Block("minecraft", "bricks")
-stone = anvil.Block("minecraft", "stone")
-grass_block = anvil.Block("minecraft", "grass_block")
-spruce_log = anvil.Block("minecraft", "spruce_log")
-birch_leaves = anvil.Block("minecraft", "birch_leaves")
-dirt = anvil.Block("minecraft", "dirt")
-sand = anvil.Block("minecraft", "sand")
-podzol = anvil.Block.from_numeric_id(3, 2)
-grass = anvil.Block.from_numeric_id(175, 2)
-farmland = anvil.Block("minecraft", "farmland")
-water = anvil.Block("minecraft", "water")
-wheat = anvil.Block("minecraft", "wheat", {"age": 7})
-carrots = anvil.Block("minecraft", "carrots", {"age": 7})
-potatoes = anvil.Block("minecraft", "potatoes", {"age": 7})
-cobblestone = anvil.Block("minecraft", "cobblestone")
-iron_block = anvil.Block("minecraft", "iron_block")
-oak_log = anvil.Block.from_numeric_id(17)
-oak_leaves = anvil.Block("minecraft", "oak_leaves")
-birch_log = anvil.Block("minecraft", "birch_log")
-white_stained_glass = anvil.Block("minecraft", "white_stained_glass")
-dark_oak_door_lower = anvil.Block("minecraft", "dark_oak_door", {"half": "lower"})
-dark_oak_door_upper = anvil.Block("minecraft", "dark_oak_door", {"half": "upper"})
-cobblestone_wall = anvil.Block("minecraft", "cobblestone_wall")
-stone_brick_slab = anvil.Block.from_numeric_id(44, 5)
-rail = anvil.Block("minecraft", "rail")
-
-red_flower = anvil.Block.from_numeric_id(38)
-yellow_flower = anvil.Block("minecraft", "dandelion")
-blue_flower = anvil.Block("minecraft", "blue_orchid")
-white_flower = anvil.Block("minecraft", "azure_bluet")
-
-white_concrete = anvil.Block("minecraft", "white_concrete")
-black_concrete = anvil.Block("minecraft", "black_concrete")
-gray_concrete = anvil.Block("minecraft", "gray_concrete")
-light_gray_concrete = anvil.Block("minecraft", "light_gray_concrete")
-green_stained_hardened_clay = anvil.Block.from_numeric_id(159, 5)
-dirt = anvil.Block("minecraft", "dirt")
-glowstone = anvil.Block("minecraft", "glowstone")
-sponge = anvil.Block("minecraft", "sponge")
-hay_bale = anvil.Block("minecraft", "hay_block")
-
-regions = {}
-for x in range(0, 3):
- for z in range(0, 3):
- regions["r." + str(x) + "." + str(z)] = anvil.EmptyRegion(0, 0)
-
-
-def setBlock(block, x, y, z):
- flooredX = floor(x / 512)
- flooredZ = floor(z / 512)
- identifier = "r." + str(flooredX) + "." + str(flooredZ)
- if identifier not in regions:
- regions[identifier] = anvil.EmptyRegion(0, 0)
- regions[identifier].set_block(block, x - flooredX * 512, y, z - flooredZ * 512)
-
-
-def fillBlocks(block, x1, y1, z1, x2, y2, z2):
- for x in range(x1, x2 + 1):
- for y in range(y1, y2 + 1):
- for z in range(z1, z2 + 1):
- if not (x == x2 + 1 or y == y2 + 1 or z == z2 + 1):
- setBlock(block, x, y, z)
-
-
mcWorldPath = args.path
if mcWorldPath[-1] == "/":
mcWorldPath = mcWorldPath[:-1]
-def saveRegion(region="all"):
- if region == "all":
- region_keys = list(regions.keys())
- for key in tqdm(
- region_keys,
- desc="Saving minecraft world",
- unit="region",
- bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt}",
- ):
- regions[key].save(mcWorldPath + "/region/" + key + ".mca")
- else:
- regions[region].save(mcWorldPath + "/region/" + region + ".mca")
- print(f"Saved {region}")
-
-
-from .tree import createTree # noqa: E402
-
-
def run():
print(
"""\n
@@ -184,238 +101,6 @@ def run():
args.debug,
args.downloader,
)
- imgarray = processData(rawdata, args)
-
- # Generate Minecraft world
- x = 0
- z = 0
- doorIncrement = 0
- ElementIncr = 0
- ElementsLen = len(imgarray)
- for i in tqdm(
- imgarray,
- desc="Generating pixels",
- unit=" pixels",
- total=ElementsLen,
- bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}]",
- ):
- z = 0
- for j in i:
- setBlock(dirt, x, 0, z)
- if j == 0: # Ground
- setBlock(grass_block, x, 1, z)
- elif j == 10: # Street
- setBlock(black_concrete, x, 1, z)
- setBlock(air, x, 2, z)
- elif j == 11: # Footway
- setBlock(gray_concrete, x, 1, z)
- setBlock(air, x, 2, z)
- elif j == 12: # Natural path
- setBlock(cobblestone, x, 1, z)
- elif j == 13: # Bridge
- setBlock(light_gray_concrete, x, 2, z)
- setBlock(light_gray_concrete, x - 1, 2, z - 1)
- setBlock(light_gray_concrete, x + 1, 2, z - 1)
- setBlock(light_gray_concrete, x + 1, 2, z + 1)
- setBlock(light_gray_concrete, x - 1, 2, z + 1)
- elif j == 14: # Railway
- setBlock(iron_block, x, 1, z)
- setBlock(rail, x, 2, z)
- elif j == 20: # Parking
- setBlock(gray_concrete, x, 1, z)
- elif j == 21: # Fountain border
- setBlock(light_gray_concrete, x, 2, z)
- setBlock(white_concrete, x, 1, z)
- elif j >= 22 and j <= 24: # Fence
- if str(j)[-1] == "2" or int(str(j[0])[-1]) == 2:
- setBlock(cobblestone_wall, x, 2, z)
- else:
- fillBlocks(cobblestone, x, 2, z, x, int(str(j[0])[-1]), z)
-
- setBlock(grass_block, x, 1, z)
- elif j == 30: # Meadow
- setBlock(grass_block, x, 1, z)
- randomChoice = randint(0, 2)
- if randomChoice == 0 or randomChoice == 1:
- setBlock(grass, x, 2, z)
- elif j == 31: # Farmland
- if x % 15 == 0 or z % 15 == 0: # Place water every 8 blocks
- setBlock(water, x, 1, z)
- else:
- setBlock(farmland, x, 1, z)
- if (
- randint(0, 75) == 0
- ): # Rarely place trees, hay bales, or leaf blocks
- special_choice = randint(1, 10)
- if special_choice <= 1: # 20% chance
- createTree(x, z, randint(1, 3))
- elif special_choice <= 6: # 40% chance
- setBlock(hay_bale, x, 2, z)
- if randint(0, 2) == 0:
- setBlock(hay_bale, x, 3, z)
- setBlock(hay_bale, x - 1, 2, z)
- setBlock(hay_bale, x, 2, z - 1)
- else:
- setBlock(hay_bale, x, 3, z)
- setBlock(hay_bale, x + 1, 2, z)
- setBlock(hay_bale, x, 2, z + 1)
- else: # Remaining 40% chance
- setBlock(oak_leaves, x, 2, z)
- else:
- crop_choice = randint(0, 2)
- crops = [wheat, carrots, potatoes]
- setBlock(crops[crop_choice], x, 2, z)
- elif j == 32: # Forest
- setBlock(grass_block, x, 1, z)
- randomChoice = randint(0, 20)
- randomTree = randint(1, 3)
- randomFlower = randint(1, 4)
- if randomChoice == 20:
- createTree(x, z, randomTree)
- elif randomChoice == 2:
- if randomFlower == 1:
- setBlock(red_flower, x, 2, z)
- elif randomFlower == 2:
- setBlock(blue_flower, x, 2, z)
- elif randomFlower == 3:
- setBlock(yellow_flower, x, 2, z)
- else:
- setBlock(white_flower, x, 2, z)
- elif randomChoice == 0 or randomChoice == 1:
- setBlock(grass, x, 2, z)
- elif j == 33: # Cemetery
- # Spawn chances
- grave_chance = 25
- flower_chance = 15
- tree_chance = 5
-
- setBlock(podzol, x, 1, z)
- if (x % 3 == 0) and (z % 3 == 0):
- randomChoice = randint(0, 100)
- if randomChoice < grave_chance:
- if randint(0, 1) == 0:
- setBlock(cobblestone, x - 1, 2, z)
- setBlock(stone_brick_slab, x - 1, 3, z)
- setBlock(stone_brick_slab, x, 2, z)
- setBlock(stone_brick_slab, x + 1, 2, z)
- else:
- setBlock(cobblestone, x, 2, z - 1)
- setBlock(stone_brick_slab, x, 3, z - 1)
- setBlock(stone_brick_slab, x, 2, z)
- setBlock(stone_brick_slab, x, 2, z + 1)
- elif randomChoice < grave_chance + flower_chance:
- setBlock(red_flower, x, 2, z)
- elif randomChoice < grave_chance + flower_chance + tree_chance:
- createTree(x, z, randint(1, 3))
- elif j == 34: # Beach
- setBlock(sand, x, 1, z)
- elif j == 35: # Wetland
- randomChoice = randint(0, 2)
- if randomChoice == 0:
- setBlock(grass_block, x, 1, z)
- else:
- setBlock(water, x, 1, z)
- elif j == 36: # Pitch
- setBlock(green_stained_hardened_clay, x, 1, z)
- elif j == 37: # Swimming pool
- setBlock(water, x, 1, z)
- setBlock(white_concrete, x, 0, z)
- elif j == 38: # Water
- setBlock(water, x, 1, z)
- elif j == 39: # Raw grass
- setBlock(grass_block, x, 1, z)
- elif j >= 50 and j <= 59: # House corner
- building_height = 5
- if j == 51:
- building_height = 8
- elif j == 52:
- building_height = 11
- elif j == 53:
- building_height = 14
- elif j == 54:
- building_height = 17
- elif j == 55:
- building_height = 20
- elif j == 56:
- building_height = 23
- elif j == 57:
- building_height = 26
- elif j == 58:
- building_height = 29
- elif j == 59:
- building_height = 32
-
- fillBlocks(white_concrete, x, 1, z, x, building_height, z)
- elif j >= 60 and j <= 69: # House wall
- building_height = 4
- if j == 61:
- building_height = 7
- elif j == 62:
- building_height = 10
- elif j == 63:
- building_height = 13
- elif j == 64:
- building_height = 16
- elif j == 65:
- building_height = 19
- elif j == 66:
- building_height = 22
- elif j == 67:
- building_height = 25
- elif j == 68:
- building_height = 28
- elif j == 69:
- building_height = 31
-
- if doorIncrement == 25:
- fillBlocks(white_stained_glass, x, 4, z, x, building_height, z)
- setBlock(white_concrete, x, 1, z)
- setBlock(dark_oak_door_lower, x, 2, z)
- setBlock(dark_oak_door_upper, x, 3, z)
- doorIncrement = 0
- else:
- fillBlocks(white_concrete, x, 1, z, x, 2, z)
- fillBlocks(white_stained_glass, x, 3, z, x, building_height, z)
- doorIncrement += 1
- setBlock(white_concrete, x, building_height + 1, z)
- elif j >= 70 and j <= 79: # House interior
- if j >= 70:
- setBlock(white_concrete, x, 5, z)
- if j >= 71:
- setBlock(white_concrete, x, 8, z)
- if j >= 72:
- setBlock(white_concrete, x, 11, z)
- if j >= 73:
- setBlock(white_concrete, x, 14, z)
- if j >= 74:
- setBlock(white_concrete, x, 17, z)
- if j >= 75:
- setBlock(white_concrete, x, 20, z)
- if j >= 76:
- setBlock(white_concrete, x, 23, z)
- if j >= 77:
- setBlock(white_concrete, x, 26, z)
- if j >= 78:
- setBlock(white_concrete, x, 29, z)
- if j >= 78:
- setBlock(
- white_concrete, x, 32, z
- )
-
- setBlock(white_concrete, x, 1, z)
-
- z += 1
- x += 1
- ElementIncr += 1
-
- print(
- f"Generation finished in {(time.time() - processStartTime):.2f} "
- + f"seconds ({((time.time() - processStartTime) / 60):.2f} minutes)"
- )
- # Saving minecraft world
- saveRegion()
- print(
- f"Done! Finished in {(time.time() - processStartTime):.2f} "
- + f"seconds ({((time.time() - processStartTime) / 60):.2f} minutes)"
- )
+ rawData = processRawData(rawdata, args)
+ generateWorld(rawData)
os._exit(0)
diff --git a/src/processData.py b/src/processData.py
index 8db09e8..c07f40d 100644
--- a/src/processData.py
+++ b/src/processData.py
@@ -1,42 +1,226 @@
-from cv2 import imwrite
+from math import floor
from time import time
from tqdm import tqdm
-import mmap
+import anvil
+import matplotlib.path as mplPath
import numpy as np
+import random
+from .blockDefinitions import *
from .bresenham import bresenham
-from .floodFill import floodFill
-OFFSET = 1000000000 # Fixed offset to ensure all coordinates are positive
-SCALE_FACTOR = 1000000 # Consistent scaling factor
+OFFSET = 1000000000 # Offset to ensure all coordinates are positive
+SCALE_FACTOR = 1000000 # Scaling factor
+regions = {}
-def create_memory_mapped_array(filename, shape, dtype):
- # Open a file in binary read-write mode
- with open(filename, "w+b") as f:
- # Seek to the end of the file to ensure it's the desired size
- f.seek((np.prod(shape) * np.dtype(dtype).itemsize) - 1)
- f.write(b"\0")
+timeout = 2
- # Memory-map the file
- with open(filename, "r+b") as f:
- # Memory-map the file, size 0 means the whole file
- mmapped_array = mmap.mmap(f.fileno(), 0)
- # Create a memory-mapped array from the memory-mapped file
- return np.ndarray(shape=shape, dtype=dtype, buffer=mmapped_array)
+def floodFillArea(polygon_coords):
+ """
+ Fast flood fill for polygons with a timeout.
+ Parameters:
+ - polygon_coords: List of (x, z) tuples defining the polygon.
+ - timeout: Maximum time (in seconds) for the flood fill to run.
-# Parsing data
-def processData(data, args):
+ Returns:
+ - A list of (x, z) tuples representing the filled area within the polygon.
+ """
+ if len(polygon_coords) < 3:
+ return []
+
+ start_time = time()
+
+ # Extract x and z coordinates
+ x_coords = [coord[0] for coord in polygon_coords]
+ z_coords = [coord[1] for coord in polygon_coords]
+
+ # Create a Path object for the polygon
+ poly_path = mplPath.Path(np.array(polygon_coords))
+
+ # Determine the bounding box of the polygon
+ min_x, max_x = min(x_coords), max(x_coords)
+ min_z, max_z = min(z_coords), max(z_coords)
+
+ # Initialize an empty list to store the filled area
+ filled_area = []
+
+ # Use numpy to efficiently check points within the bounding box
+ x_range = np.arange(min_x, max_x + 1)
+ z_range = np.arange(min_z, max_z + 1)
+
+ # Create a grid of points to test within the bounding box
+ xv, zv = np.meshgrid(x_range, z_range, indexing="xy")
+ points_to_check = np.vstack((xv.ravel(), zv.ravel())).T
+
+ # Filter points within the polygon
+ within_polygon = poly_path.contains_points(points_to_check)
+
+ # Gather the points within the polygon
+ filled_area = [
+ tuple(points_to_check[i])
+ for i in range(len(points_to_check))
+ if within_polygon[i]
+ ]
+
+ # Check for timeout
+ if time() - start_time > timeout:
+ return filled_area
+
+ return filled_area
+
+
+def checkForWater(x, z):
+ flooredX = floor(x / 512)
+ flooredZ = floor(z / 512)
+ identifier = "r." + str(flooredX) + "." + str(flooredZ)
+
+ if identifier not in regions:
+ return False
+
+ chunkX = (x // 16) % 32
+ chunkZ = (z // 16) % 32
+
+ try:
+ chunk = regions[identifier].get_chunk(chunkX, chunkZ)
+ except anvil.errors.ChunkNotFound:
+ return False
+
+ local_x = x % 16
+ local_z = z % 16
+ waterlevel_block = chunk.get_block(local_x, 2, local_z)
+
+ return waterlevel_block is not None and waterlevel_block.name() == "minecraft:water"
+
+
+def setBlock(block, x, y, z, overrideAll=False):
+ flooredX = floor(x / 512)
+ flooredZ = floor(z / 512)
+ identifier = "r." + str(flooredX) + "." + str(flooredZ)
+
+ if identifier not in regions:
+ regions[identifier] = anvil.EmptyRegion(0, 0)
+
+ chunkX = (x // 16) % 32
+ chunkZ = (z // 16) % 32
+
+ try:
+ chunk = regions[identifier].get_chunk(chunkX, chunkZ)
+ except anvil.errors.ChunkNotFound:
+ chunk = None
+
+ if chunk is None:
+ chunk = anvil.EmptyChunk(chunkX, chunkZ)
+ regions[identifier].add_chunk(chunk)
+
+ # Get the block within the chunk
+ local_x = x % 16
+ local_z = z % 16
+ y = max(0, min(y, 255))
+
+ existing_block = chunk.get_block(local_x, y, local_z)
+ block_below = chunk.get_block(local_x, y - 1, local_z) if y > 1 else grass
+ block_below = block_below or grass
+
+ # Check for overwrite rules
+ whitelist_existing_block = [
+ "minecraft:air",
+ "minecraft:dirt",
+ "minecraft:grass_block",
+ ]
+ whitelist_block_below = [
+ "minecraft:water",
+ "minecraft:light_gray_concrete",
+ "minecraft:white_concrete",
+ "minecraft:black_concrete",
+ "minecraft:gray_concrete",
+ "minecraft:dark_oak_door",
+ ]
+ if overrideAll or (
+ (existing_block is None or existing_block.name() in whitelist_existing_block)
+ and block_below.name() not in whitelist_block_below
+ ):
+ chunk.set_block(block, local_x, y, local_z)
+
+
+def fillBlocks(block, x1, y1, z1, x2, y2, z2, overrideAll=False):
+ for x in range(x1, x2 + 1):
+ for y in range(y1, y2 + 1):
+ for z in range(z1, z2 + 1):
+ if not (x == x2 + 1 or y == y2 + 1 or z == z2 + 1):
+ setBlock(block, x, y, z, overrideAll)
+
+
+def initializeGroundLayer(minMaxDistX, minMaxDistY):
+ print("Generating ground layer...")
+ chunkSizeX = minMaxDistX // 16
+ chunkSizeY = minMaxDistY // 16
+
+ for chunkX in range(chunkSizeX + 1):
+ for chunkY in range(chunkSizeY + 1):
+ startX = chunkX * 16
+ startY = chunkY * 16
+
+ fillBlocks(cobblestone, startX, 0, startY, startX + 15, 0, startY + 15)
+ fillBlocks(dirt, startX, 1, startY, startX + 15, 1, startY + 15)
+ fillBlocks(grass_block, startX, 2, startY, startX + 15, 2, startY + 15)
+
+
+def saveRegions(region="all"):
+ if region == "all":
+ region_keys = list(regions.keys())
+ for key in tqdm(
+ region_keys,
+ desc="Saving minecraft world",
+ unit="region",
+ bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt}",
+ ):
+ regions[key].save(mcWorldPath + "/region/" + key + ".mca")
+ else:
+ regions[region].save(mcWorldPath + "/region/" + region + ".mca")
+ print(f"Saved {region}")
+
+
+priority_order = ["building", "highway", "waterway", "barrier"]
+
+
+def get_priority(element):
+ for i, tag in enumerate(priority_order):
+ if tag in element["tags"]:
+ return i
+ return len(priority_order)
+
+
+from .tree import createTree # noqa: E402
+
+# Parse raw data
+minMaxDistX = 0
+minMaxDistY = 0
+startTime = 0
+mcWorldPath = ""
+
+
+def processRawData(data, args):
+ global startTime, minMaxDistX, minMaxDistY, mcWorldPath, timeout
+ print("Parsing data...")
resDownScaler = 10
- processingStartTime = time()
+ startTime = time()
+
+ mcWorldPath = args.path
+ if mcWorldPath[-1] == "/":
+ mcWorldPath = mcWorldPath[:-1]
+
+ timeout = args.timeout
greatestElementX = -OFFSET
greatestElementY = -OFFSET
lowestElementX = OFFSET
lowestElementY = OFFSET
+ included_ways = []
+
# Convert all coordinates and determine bounds
for element in data["elements"]:
if element["type"] == "node":
@@ -52,6 +236,53 @@ def processData(data, args):
if element["lon"] < lowestElementY:
lowestElementY = element["lon"]
+ if "tags" in element:
+ node_whitelist = [
+ "door",
+ "entrance",
+ "natural",
+ "amenity",
+ "barrier",
+ "vending",
+ "highway",
+ ]
+ if any(tag in element["tags"] for tag in node_whitelist):
+ if (
+ "natural" in element["tags"]
+ and "tree" not in element["tags"]["natural"]
+ ):
+ continue
+
+ if "amenity" in element["tags"] and (
+ "waste_basket" not in element["tags"]["amenity"]
+ or "bench" not in element["tags"]["amenity"]
+ or "vending_machine" not in element["tags"]["amenity"]
+ ):
+ continue
+
+ if (
+ "barrier" in element["tags"]
+ and "bollard" not in element["tags"]["barrier"]
+ ):
+ continue
+
+ if (
+ "highway" in element["tags"]
+ and "street_lamp" not in element["tags"]["highway"]
+ ):
+ continue
+
+ # Create a temporary way element from the node
+ way = {
+ "type": "way",
+ "id": element["id"],
+ "nodes": [element["id"]],
+ "tags": element["tags"],
+ }
+ included_ways.append(way)
+
+ data["elements"].extend(included_ways)
+
if args.debug:
print(
f"greatestElementX: {greatestElementX}, greatestElementY: {greatestElementY}"
@@ -71,7 +302,7 @@ def processData(data, args):
minBuilding = (greatestElementX, greatestElementY)
nodeIndexList = []
for i, element in enumerate(data["elements"]):
- if element["type"] == "way":
+ if element["type"] == "way" and "tags" in element:
for j, node in enumerate(element["nodes"]):
element["nodes"][j] = nodesDict[node]
@@ -80,14 +311,14 @@ def processData(data, args):
orig_posDeterminationCoordX = element["nodes"][0][0]
orig_posDeterminationCoordY = element["nodes"][0][1]
map_posDeterminationCoordX = round(
- element["nodes"][0][0] / resDownScaler
+ element["nodes"][0][0] / (resDownScaler * 70 / 100)
)
map_posDeterminationCoordY = round(
element["nodes"][0][1] / resDownScaler
)
for coordinate in element["nodes"]:
- cordX = round(coordinate[0] / resDownScaler)
+ cordX = round(coordinate[0] / (resDownScaler * 70 / 100))
cordY = round(coordinate[1] / resDownScaler)
if cordX > maxBuilding[0]:
@@ -112,10 +343,11 @@ def processData(data, args):
minMaxDistY = maxBuilding[1] - minBuilding[1]
for i, element in enumerate(data["elements"]):
- if element["type"] == "way":
+ if element["type"] == "way" and "tags" in element:
for j, node in enumerate(element["nodes"]):
subtractedMinX = (
- round(element["nodes"][j][0] / resDownScaler) - minBuilding[0]
+ round(element["nodes"][j][0] / (resDownScaler * 70 / 100))
+ - minBuilding[0]
)
subtractedMinY = (
round(element["nodes"][j][1] / resDownScaler) - minBuilding[1]
@@ -139,6 +371,9 @@ def processData(data, args):
if element["nodes"][j][1] >= minMaxDistY:
element["nodes"][j][1] = minMaxDistY - 1
+ # Sort elements by priority for layers
+ data["elements"].sort(key=get_priority)
+
if args.debug:
print(f"minMaxDistX: {minMaxDistX}")
print(f"minMaxDistY: {minMaxDistY}")
@@ -157,96 +392,108 @@ def processData(data, args):
with open("arnis-debug-processed_data.json", "w", encoding="utf-8") as f:
f.write(str(data))
- # Create a memory-mapped array for the image
- img = create_memory_mapped_array(
- "image.img", (minMaxDistY, minMaxDistX, 1), np.uint8
- )
+ return data
- img.fill(0)
- imgLanduse = img.copy()
- # Processing data
- ElementIncr = 0
- ElementsLen = len(data["elements"])
+# Generate Minecraft world
+def generateWorld(data):
+ initializeGroundLayer(minMaxDistX, minMaxDistY)
+
+ # Process sorted elements
+ groundLevel = 2
for element in tqdm(
- reversed(data["elements"]),
+ data["elements"],
desc="Processing elements",
unit=" elements",
- total=ElementsLen,
+ total=len(data["elements"]),
bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}]",
):
- if element["type"] == "way" and "tags" in element:
+ if element["type"] == "way":
+ # Buildings
if "building" in element["tags"]:
previousElement = (0, 0)
cornerAddup = (0, 0, 0)
currentBuilding = np.array([[0, 0]])
+
+ # Determine the block variation index
+ variation_index = random.randint(0, len(building_corner_variations) - 1)
+
+ corner_block = building_corner_variations[variation_index]
+ wall_block = building_wall_variations[variation_index]
+ floor_block = building_floor_variations[variation_index]
+
for coordinate in element["nodes"]:
- buildingHeight = 1
+ buildingHeight = 4 # Default building height
if previousElement != (0, 0):
- if "height" in element["tags"]:
- if len(element["tags"]["height"]) >= 3:
- buildingHeight = 9
- elif len(element["tags"]["height"]) == 1:
- buildingHeight = 2
- elif element["tags"]["height"][:1] == "1":
- buildingHeight = 3
- elif element["tags"]["height"][:1] == "2":
- buildingHeight = 6
- else:
- buildingHeight = 9
+ # Check for height attribute
+ if (
+ "height" in element["tags"]
+ and element["tags"]["height"].isdigit()
+ ):
+ buildingHeight = int(element["tags"]["height"])
+ # Check for levels attribute
if (
"building:levels" in element["tags"]
- and element["tags"]["building:levels"].isnumeric()
- and int(float(element["tags"]["building:levels"])) <= 8
- and int(float(element["tags"]["building:levels"])) >= 1
+ and element["tags"]["building:levels"].isdigit()
):
- buildingHeight = str(
- int(float(element["tags"]["building:levels"])) - 1
- )
+ levels = int(element["tags"]["building:levels"])
+ if levels >= 1 and (levels * 3) > buildingHeight:
+ buildingHeight = (
+ levels * 3
+ ) # Two blocks per level + one for ceiling
+ # Check for special tags
if (
"building" in element["tags"]
and element["tags"]["building"] == "garage"
):
- buildingHeight = 0
+ buildingHeight = (
+ 2 # Garages have a fixed height of 2 blocks
+ )
- for i in bresenham(
+ # Calculate walls and corners
+ for coordBresenham in bresenham(
coordinate[0],
coordinate[1],
previousElement[0],
previousElement[1],
):
- if not (
- str(img[i[1]][i[0]][0])[:1] == "6"
- and img[i[1]][i[0]][0] > int("6" + str(buildingHeight))
+ for h in range(
+ groundLevel + 1, groundLevel + buildingHeight + 1
):
- img[i[1]][i[0]] = int("6" + str(buildingHeight))
+ if (
+ coordBresenham[0] == element["nodes"][0][0]
+ and coordBresenham[1] == element["nodes"][0][1]
+ ):
+ setBlock(
+ corner_block,
+ coordBresenham[0],
+ h,
+ coordBresenham[1],
+ overrideAll=True,
+ )
+ else:
+ setBlock(
+ wall_block,
+ coordBresenham[0],
+ h,
+ coordBresenham[1],
+ overrideAll=True,
+ )
+ setBlock(
+ cobblestone,
+ coordBresenham[0],
+ groundLevel + buildingHeight + 1,
+ coordBresenham[1],
+ overrideAll=True,
+ )
currentBuilding = np.append(
currentBuilding, [[coordinate[0], coordinate[1]]], axis=0
)
- if not (
- str(img[coordinate[1]][coordinate[0]][0])[:1] == "5"
- and img[coordinate[1]][coordinate[0]][0]
- > int("5" + str(buildingHeight))
- ):
- img[coordinate[1]][coordinate[0]] = int(
- "5" + str(buildingHeight)
- )
-
- if not (
- str(img[previousElement[1]][previousElement[0]][0])[:1]
- == "5"
- and img[previousElement[1]][previousElement[0]][0]
- > int("5" + str(buildingHeight))
- ):
- img[previousElement[1]][previousElement[0]] = int(
- "5" + str(buildingHeight)
- )
-
cornerAddup = (
cornerAddup[0] + coordinate[0],
cornerAddup[1] + coordinate[1],
@@ -254,223 +501,634 @@ def processData(data, args):
)
previousElement = (coordinate[0], coordinate[1])
+ # Floodfill interior with floor variation
if cornerAddup != (0, 0, 0):
- img = floodFill(
- img,
- round(cornerAddup[1] / cornerAddup[2]),
- round(cornerAddup[0] / cornerAddup[2]),
- int("7" + str(buildingHeight)),
- currentBuilding,
- minMaxDistX,
- minMaxDistY,
- elementType="building",
- )
+ polygon_coords = [
+ (coord[0], coord[1]) for coord in element["nodes"]
+ ]
+ floor_area = floodFillArea(polygon_coords)
+
+ for x, z in floor_area:
+ # Set floor
+ setBlock(floor_block, x, groundLevel, z, overrideAll=True)
+
+ # Set level ceilings
+ if buildingHeight > 4:
+ for h in range(
+ groundLevel + 4, groundLevel + buildingHeight - 2, 4
+ ):
+ if (x % 6 == 0) and (z % 6 == 0):
+ setBlock(glowstone, x, h, z)
+ else:
+ setBlock(floor_block, x, h, z, overrideAll=True)
+ else:
+ if (x % 6 == 0) and (z % 6 == 0):
+ setBlock(glowstone, x, groundLevel + buildingHeight, z)
+
+ # Set house ceiling
+ setBlock(
+ floor_block,
+ x,
+ groundLevel + buildingHeight + 1,
+ z,
+ overrideAll=True,
+ )
- elif "highway" in element["tags"]:
- previousElement = (0, 0)
- for coordinate in element["nodes"]:
- highwayType = 10
- if (
- previousElement != (0, 0)
- and element["tags"]["highway"] != "corridor"
- and previousElement != (0, 0)
- and element["tags"]["highway"] != "steps"
- and element["tags"]["highway"] != "bridge"
- and element["tags"]["highway"] != "proposed"
- ):
- blockRange = 2
- highwayType = 10
+ # Doors
+ elif "door" in element["tags"] or "entrance" in element["tags"]:
+ if (
+ "level" in element["tags"]
+ and element["tags"]["level"].isdigit()
+ and int(element["tags"]["level"]) != 0
+ ):
+ continue
+
+ node_id = element["nodes"][0]
+ x = node_id[0]
+ z = node_id[1]
+ setBlock(gray_concrete, x, groundLevel, z, overrideAll=True)
+ setBlock(dark_oak_door_lower, x, groundLevel + 1, z, overrideAll=True)
+ setBlock(dark_oak_door_upper, x, groundLevel + 2, z, overrideAll=True)
+
+ # Highways
+ elif "highway" in element["tags"]:
+ if "street_lamp" in element["tags"]["highway"]:
+ node_id = element["nodes"][0]
+ x = node_id[0]
+ z = node_id[1]
+ setBlock(oak_fence, x, groundLevel + 1, z)
+ setBlock(oak_fence, x, groundLevel + 2, z)
+ setBlock(oak_fence, x, groundLevel + 3, z)
+ setBlock(oak_fence, x, groundLevel + 4, z)
+ setBlock(glowstone, x, groundLevel + 5, z)
+ else:
+ previousElement = None
+ blockRange = 2
+ blockType = black_concrete
+
+ highway_tag = element["tags"]["highway"]
+ lanes = element["tags"].get("lanes")
+
+ # Determine block range and type based on highway type
+ if highway_tag == "footway":
+ blockType = gray_concrete
+ blockRange = 1
+ elif highway_tag == "path":
+ blockType = light_gray_concrete
+ blockRange = 1
+ elif highway_tag in ["path", "footway"]:
if (
- element["tags"]["highway"] == "path"
- or element["tags"]["highway"] == "footway"
+ "footway" in element["tags"]
+ and element["tags"]["footway"] == "crossing"
):
blockRange = 1
- highwayType = 11
- elif element["tags"]["highway"] == "motorway":
- blockRange = 4
- elif element["tags"]["highway"] == "track":
- blockRange = 1
- highwayType = 12
- elif (
- "lanes" in element["tags"]
- and element["tags"]["lanes"] != "1"
- and element["tags"]["lanes"] != "2"
- ):
- blockRange = 4
-
- for i in bresenham(
- coordinate[0],
- coordinate[1],
- previousElement[0],
- previousElement[1],
- ):
- for x in range(i[0] - blockRange, i[0] + blockRange + 1):
- for y in range(
- i[1] - blockRange, i[1] + blockRange + 1
+ blockType = white_concrete # For the zebra crossing
+ elif highway_tag == "motorway":
+ blockRange = 4
+ elif highway_tag == "track":
+ blockRange = 1
+ elif lanes and lanes not in ["1", "2"]:
+ blockRange = 4
+
+ for coordinate in element["nodes"]:
+ if previousElement is not None:
+ if highway_tag == "bridge":
+ for coordBresenham in bresenham(
+ coordinate[0],
+ coordinate[1],
+ previousElement[0],
+ previousElement[1],
):
- if (
- x < minMaxDistX
- and y < minMaxDistY
- and img[y][x] == 0
+ for x in range(
+ coordBresenham[0] - blockRange,
+ coordBresenham[0] + blockRange + 1,
):
- img[y][x] = highwayType
- previousElement = (coordinate[0], coordinate[1])
-
+ for y in range(
+ coordBresenham[1] - blockRange,
+ coordBresenham[1] + blockRange + 1,
+ ):
+ height = (
+ groundLevel
+ + 1
+ + (
+ abs(
+ coordinate[1]
+ - previousElement[1]
+ )
+ // 16
+ )
+ ) # Gradual elevation
+ setBlock(light_gray_concrete, x, height, y)
+ setBlock(
+ cobblestone_wall, x, height + 1, y
+ ) # Railings
+ elif highway_tag == "steps":
+ for coordBresenham in bresenham(
+ coordinate[0],
+ coordinate[1],
+ previousElement[0],
+ previousElement[1],
+ ):
+ for x in range(
+ coordBresenham[0] - blockRange,
+ coordBresenham[0] + blockRange + 1,
+ ):
+ for y in range(
+ coordBresenham[1] - blockRange,
+ coordBresenham[1] + blockRange + 1,
+ ):
+ height = groundLevel + (
+ abs(coordinate[1] - previousElement[1])
+ // 16
+ ) # Elevate for steps
+ setBlock(stone, x, height, y)
+ else:
+ for coordBresenham in bresenham(
+ coordinate[0],
+ coordinate[1],
+ previousElement[0],
+ previousElement[1],
+ ):
+ for x in range(
+ coordBresenham[0] - blockRange,
+ coordBresenham[0] + blockRange + 1,
+ ):
+ for y in range(
+ coordBresenham[1] - blockRange,
+ coordBresenham[1] + blockRange + 1,
+ ):
+ if (
+ highway_tag == "footway"
+ and element["tags"].get("footway")
+ == "crossing"
+ ):
+ is_horizontal = abs(
+ coordinate[0] - previousElement[0]
+ ) >= abs(
+ coordinate[1] - previousElement[1]
+ )
+ if is_horizontal:
+ if coordBresenham[0] % 4 < 2:
+ setBlock(
+ white_concrete,
+ x,
+ groundLevel,
+ y,
+ )
+ else:
+ setBlock(
+ black_concrete,
+ x,
+ groundLevel,
+ y,
+ )
+ else:
+ if coordBresenham[1] % 4 < 2:
+ setBlock(
+ white_concrete,
+ x,
+ groundLevel,
+ y,
+ )
+ else:
+ setBlock(
+ black_concrete,
+ x,
+ groundLevel,
+ y,
+ )
+ else:
+ setBlock(blockType, x, groundLevel, y)
+ previousElement = coordinate
+
+ # Landuse
elif "landuse" in element["tags"]:
- previousElement = (0, 0)
- cornerAddup = (0, 0, 0)
- currentLanduse = np.array([[0, 0]])
+ previousElement = None
+ cornerAddup = np.array([0, 0, 0])
+ currentLanduse = []
+
+ landuse_tag = element["tags"]["landuse"]
+ blockType = {
+ "greenfield": grass_block,
+ "meadow": grass_block,
+ "grass": grass_block,
+ "farmland": farmland,
+ "forest": grass_block,
+ "cemetery": podzol,
+ "beach": sand,
+ "construction": dirt,
+ "traffic_island": stone_block_slab,
+ }.get(landuse_tag, grass_block)
+
+ # Process landuse nodes
for coordinate in element["nodes"]:
- landuseType = 39
- if (
- previousElement != (0, 0)
- and element["tags"]["landuse"] != "industrial"
- and element["tags"]["landuse"] != "residential"
- ):
- if (
- element["tags"]["landuse"] == "greenfield"
- or element["tags"]["landuse"] == "meadow"
- or element["tags"]["landuse"] == "grass"
- ):
- landuseType = 30
- elif element["tags"]["landuse"] == "farmland":
- landuseType = 31
- elif element["tags"]["landuse"] == "forest":
- landuseType = 32
- elif element["tags"]["landuse"] == "cemetery":
- landuseType = 33
- elif element["tags"]["landuse"] == "beach":
- landuseType = 34
-
- for i in bresenham(
+ if previousElement is not None:
+ for coordBresenham in bresenham(
coordinate[0],
coordinate[1],
previousElement[0],
previousElement[1],
):
- if imgLanduse[i[1]][i[0]] == 0:
- imgLanduse[i[1]][i[0]] = landuseType
-
- currentLanduse = np.append(
- currentLanduse, [[coordinate[0], coordinate[1]]], axis=0
- )
- cornerAddup = (
- cornerAddup[0] + coordinate[0],
- cornerAddup[1] + coordinate[1],
- cornerAddup[2] + 1,
- )
- previousElement = (coordinate[0], coordinate[1])
-
- if cornerAddup != (0, 0, 0):
- imgLanduse = floodFill(
- imgLanduse,
- round(cornerAddup[1] / cornerAddup[2]),
- round(cornerAddup[0] / cornerAddup[2]),
- landuseType,
- currentLanduse,
- minMaxDistX,
- minMaxDistY,
- )
+ setBlock(
+ grass_block,
+ coordBresenham[0],
+ groundLevel,
+ coordBresenham[1],
+ )
+ currentLanduse.append((coordinate[0], coordinate[1]))
+ cornerAddup += np.array([coordinate[0], coordinate[1], 1])
+
+ previousElement = coordinate
+
+ if len(currentLanduse) > 0:
+ polygon_coords = [
+ (coord[0], coord[1]) for coord in element["nodes"]
+ ]
+ floor_area = floodFillArea(polygon_coords)
+
+ for x, z in floor_area:
+ setBlock(blockType, x, groundLevel, z)
+ if landuse_tag == "traffic_island":
+ setBlock(blockType, x, groundLevel + 1, z)
+
+ # Add specific features for different landuse types
+ if landuse_tag == "cemetery":
+ if (x % 3 == 0) and (z % 3 == 0):
+ randomChoice = random.randint(0, 100)
+ if randomChoice < 15: # 15% chance for a grave
+ if random.randint(0, 1) == 0:
+ setBlock(cobblestone, x - 1, groundLevel + 1, z)
+ setBlock(
+ stone_brick_slab, x - 1, groundLevel + 2, z
+ )
+ setBlock(
+ stone_brick_slab, x, groundLevel + 1, z
+ )
+ setBlock(
+ stone_brick_slab, x + 1, groundLevel + 1, z
+ )
+ else:
+ setBlock(cobblestone, x, groundLevel + 1, z - 1)
+ setBlock(
+ stone_brick_slab, x, groundLevel + 2, z - 1
+ )
+ setBlock(
+ stone_brick_slab, x, groundLevel + 1, z
+ )
+ setBlock(
+ stone_brick_slab, x, groundLevel + 1, z + 1
+ )
+ elif randomChoice < 30: # 15% chance for a flower
+ setBlock(red_flower, x, groundLevel + 1, z)
+ elif randomChoice < 33: # 3% chance for a tree
+ createTree(
+ x, groundLevel + 1, z, random.randint(1, 3)
+ )
+
+ elif landuse_tag == "forest":
+ if checkForWater(x, z):
+ continue
+ randomChoice = random.randint(0, 20)
+ randomTree = random.randint(1, 3)
+ randomFlower = random.randint(1, 4)
+ if randomChoice == 20:
+ createTree(x, groundLevel + 1, z, randomTree)
+ elif randomChoice == 2:
+ if randomFlower == 1:
+ setBlock(red_flower, x, groundLevel + 1, z)
+ elif randomFlower == 2:
+ setBlock(blue_flower, x, groundLevel + 1, z)
+ elif randomFlower == 3:
+ setBlock(yellow_flower, x, groundLevel + 1, z)
+ else:
+ setBlock(white_flower, x, groundLevel + 1, z)
+ elif randomChoice == 0 or randomChoice == 1:
+ setBlock(grass, x, groundLevel + 1, z)
+
+ elif landuse_tag == "farmland":
+ if checkForWater(x, z):
+ continue
+ if x % 15 == 0 or z % 15 == 0:
+ setBlock(water, x, groundLevel, z, overrideAll=True)
+ else:
+ setBlock(farmland, x, groundLevel, z)
+ if (
+ random.randint(0, 75) == 0
+ ): # Rarely place trees, hay bales, or leaves
+ special_choice = random.randint(1, 10)
+ if special_choice <= 1: # 10% chance for a tree
+ createTree(
+ x, groundLevel + 1, z, random.randint(1, 3)
+ )
+ elif (
+ special_choice <= 6
+ ): # 50% chance for hay bales
+ setBlock(hay_bale, x, groundLevel + 1, z)
+ if random.randint(0, 2) == 0:
+ setBlock(hay_bale, x, groundLevel + 2, z)
+ setBlock(
+ hay_bale, x - 1, groundLevel + 1, z
+ )
+ setBlock(
+ hay_bale, x, groundLevel + 1, z - 1
+ )
+ else:
+ setBlock(hay_bale, x, groundLevel + 2, z)
+ setBlock(
+ hay_bale, x + 1, groundLevel + 1, z
+ )
+ setBlock(
+ hay_bale, x, groundLevel + 1, z + 1
+ )
+ else: # 40% chance for leaves
+ setBlock(oak_leaves, x, groundLevel + 1, z)
+ else: # Otherwise, place crops
+ crop_choice = random.randint(0, 2)
+ crops = [wheat, carrots, potatoes]
+ setBlock(crops[crop_choice], x, groundLevel + 1, z)
+
+ elif landuse_tag == "construction":
+ randomChoice = random.randint(0, 1500)
+
+ # Random chance distribution
+ if randomChoice < 6: # Scaffolding
+ setBlock(scaffolding, x, groundLevel + 1, z)
+ if randomChoice < 2:
+ setBlock(scaffolding, x, groundLevel + 2, z)
+ setBlock(scaffolding, x, groundLevel + 3, z)
+ elif randomChoice < 4:
+ setBlock(scaffolding, x, groundLevel + 2, z)
+ setBlock(scaffolding, x, groundLevel + 3, z)
+ setBlock(scaffolding, x, groundLevel + 4, z)
+ setBlock(scaffolding, x, groundLevel + 1, z + 1)
+ else:
+ setBlock(scaffolding, x, groundLevel + 2, z)
+ setBlock(scaffolding, x, groundLevel + 3, z)
+ setBlock(scaffolding, x, groundLevel + 4, z)
+ setBlock(scaffolding, x, groundLevel + 5, z)
+ setBlock(scaffolding, x - 1, groundLevel + 1, z)
+ setBlock(scaffolding, x + 1, groundLevel + 1, z - 1)
+ elif randomChoice < 20: # Random blocks
+ construction_items = [
+ oak_log,
+ cobblestone,
+ gravel,
+ glowstone,
+ stone,
+ cobblestone_wall,
+ black_concrete,
+ sand,
+ oak_planks,
+ dirt,
+ brick,
+ ]
+ setBlock(
+ random.choice(construction_items),
+ x,
+ groundLevel + 1,
+ z,
+ )
+ elif randomChoice < 25: # Dirt pile
+ if randomChoice < 20:
+ setBlock(dirt, x, groundLevel + 1, z)
+ setBlock(dirt, x, groundLevel + 2, z)
+ setBlock(dirt, x + 1, groundLevel + 1, z)
+ setBlock(dirt, x, groundLevel + 1, z + 1)
+ else:
+ setBlock(dirt, x, groundLevel + 1, z)
+ setBlock(dirt, x, groundLevel + 2, z)
+ setBlock(dirt, x - 1, groundLevel + 1, z)
+ setBlock(dirt, x, groundLevel + 1, z - 1)
+ elif randomChoice < 140: # Ground roughness
+ setBlock(air, x, groundLevel, z, overrideAll=True)
+
+ elif landuse_tag == "grass":
+ if random.randint(1, 7) != 0:
+ if checkForWater(x, z):
+ continue
+ setBlock(grass, x, groundLevel + 1, z)
+
+ elif landuse_tag == "meadow":
+ if checkForWater(x, z):
+ continue
+ randomChoice = random.randint(0, 1000)
+ if randomChoice < 5:
+ createTree(x, groundLevel + 1, z, random.randint(1, 3))
+ elif randomChoice < 800:
+ setBlock(grass, x, groundLevel + 1, z)
+
+ # Natural
elif "natural" in element["tags"]:
- previousElement = (0, 0)
- cornerAddup = (0, 0, 0)
- currentNatural = np.array([[0, 0]])
- for coordinate in element["nodes"]:
- naturalType = 39
- if previousElement != (0, 0):
- if (
- element["tags"]["natural"] == "scrub"
- or element["tags"]["natural"] == "grassland"
- ):
- naturalType = 30
- elif (
- element["tags"]["natural"] == "beach"
- or element["tags"]["natural"] == "sand"
- ):
- naturalType = 34
- elif (
- element["tags"]["natural"] == "wood"
- or element["tags"]["natural"] == "tree_row"
- ):
- naturalType = 32
- elif element["tags"]["natural"] == "wetland":
- naturalType = 35
- elif element["tags"]["natural"] == "water":
- naturalType = 38
-
- for i in bresenham(
- coordinate[0],
- coordinate[1],
- previousElement[0],
- previousElement[1],
- ):
- if imgLanduse[i[1]][i[0]] == 0:
- imgLanduse[i[1]][i[0]] = naturalType
-
- currentNatural = np.append(
- currentNatural, [[coordinate[0], coordinate[1]]], axis=0
- )
- cornerAddup = (
- cornerAddup[0] + coordinate[0],
- cornerAddup[1] + coordinate[1],
- cornerAddup[2] + 1,
- )
- previousElement = (coordinate[0], coordinate[1])
+ if "tree" in element["tags"]["natural"]:
+ node_id = element["nodes"][0]
+ x = node_id[0]
+ z = node_id[1]
+ createTree(x, groundLevel + 1, z, random.randint(1, 3))
+ else:
+ previousElement = (0, 0)
+ cornerAddup = (0, 0, 0)
+ currentNatural = np.array([[0, 0]])
+ blockType = grass_block
+
+ natural_type_mapping = {
+ "scrub": grass_block,
+ "grassland": grass_block,
+ "beach": sand,
+ "sand": sand,
+ "wood": grass_block,
+ "tree_row": oak_leaves,
+ "wetland": water,
+ "water": water,
+ }
+
+ blockType = natural_type_mapping.get(
+ element["tags"]["natural"], grass_block
+ )
- if cornerAddup != (0, 0, 0):
- if naturalType != 32:
- imgLanduse = floodFill(
- imgLanduse,
- round(cornerAddup[1] / cornerAddup[2]),
- round(cornerAddup[0] / cornerAddup[2]),
- naturalType,
- currentNatural,
- minMaxDistX,
- minMaxDistY,
- )
- else:
- imgLanduse = floodFill(
- imgLanduse,
- round(cornerAddup[1] / cornerAddup[2]),
- round(cornerAddup[0] / cornerAddup[2]),
- naturalType,
- currentNatural,
- minMaxDistX,
- minMaxDistY,
- elementType="tree_row",
+ for coordinate in element["nodes"]:
+ if previousElement != (0, 0):
+ for coordBresenham in bresenham(
+ coordinate[0],
+ coordinate[1],
+ previousElement[0],
+ previousElement[1],
+ ):
+ setBlock(
+ blockType,
+ coordBresenham[0],
+ groundLevel,
+ coordBresenham[1],
+ )
+
+ currentNatural = np.append(
+ currentNatural, [[coordinate[0], coordinate[1]]], axis=0
+ )
+ cornerAddup = (
+ cornerAddup[0] + coordinate[0],
+ cornerAddup[1] + coordinate[1],
+ cornerAddup[2] + 1,
+ )
+ previousElement = (coordinate[0], coordinate[1])
+
+ if cornerAddup != (0, 0, 0):
+ polygon_coords = [
+ (coord[0], coord[1]) for coord in element["nodes"]
+ ]
+ filled_area = floodFillArea(polygon_coords)
+
+ for x, z in filled_area:
+ setBlock(blockType, x, groundLevel, z)
+
+ # Forest element generation
+ if element["tags"]["natural"] in ["wood", "tree_row"]:
+ if checkForWater(x, z):
+ continue
+ randomChoice = random.randint(0, 25)
+ randomTree = random.randint(1, 3)
+ randomFlower = random.randint(1, 4)
+ if randomChoice == 25:
+ createTree(x, groundLevel + 1, z, randomTree)
+ elif randomChoice == 2:
+ if randomFlower == 1:
+ setBlock(red_flower, x, groundLevel + 1, z)
+ elif randomFlower == 2:
+ setBlock(blue_flower, x, groundLevel + 1, z)
+ elif randomFlower == 3:
+ setBlock(yellow_flower, x, groundLevel + 1, z)
+ else:
+ setBlock(white_flower, x, groundLevel + 1, z)
+ elif randomChoice in [0, 1]:
+ setBlock(grass, x, groundLevel + 1, z)
+
+ # Amenities
+ elif "amenity" in element["tags"]:
+ if (
+ "waste_disposal" in element["tags"]["amenity"]
+ or "waste_basket" in element["tags"]["amenity"]
+ ):
+ node_id = element["nodes"][0]
+ x = node_id[0]
+ z = node_id[1]
+ setBlock(cauldron, x, groundLevel + 1, z)
+ elif "bench" in element["tags"]["amenity"]:
+ node_id = element["nodes"][0]
+ x = node_id[0]
+ z = node_id[1]
+ setBlock(
+ anvil.Block("minecraft", "oak_slab", {"type": "top"}),
+ x,
+ groundLevel + 1,
+ z,
+ )
+ setBlock(oak_log, x + 1, groundLevel + 1, z)
+ setBlock(oak_log, x - 1, groundLevel + 1, z)
+ elif "vending" in element["tags"]:
+ node_id = element["nodes"][0]
+ x = node_id[0]
+ z = node_id[1]
+
+ setBlock(iron_block, x, groundLevel + 1, z)
+ setBlock(iron_block, x, groundLevel + 2, z)
+ else:
+ previousElement = None
+ cornerAddup = np.array([0, 0, 0])
+ currentAmenity = []
+
+ for coordinate in element["nodes"]:
+ if previousElement:
+ if element["tags"]["amenity"] in ["parking", "fountain"]:
+ blockType = (
+ water
+ if element["tags"]["amenity"] == "fountain"
+ else gray_concrete
+ )
+
+ for coordBresenham in bresenham(
+ coordinate[0],
+ coordinate[1],
+ previousElement[0],
+ previousElement[1],
+ ):
+ setBlock(
+ blockType,
+ coordBresenham[0],
+ groundLevel,
+ coordBresenham[1],
+ )
+
+ if element["tags"]["amenity"] == "fountain":
+ # Decorative border around the fountain
+ for dx in [-1, 0, 1]:
+ for dz in [-1, 0, 1]:
+ if (dx, dz) != (0, 0):
+ setBlock(
+ light_gray_concrete,
+ coordBresenham[0] + dx,
+ groundLevel,
+ coordBresenham[1] + dz,
+ )
+
+ currentAmenity.append(coordinate)
+ cornerAddup += np.array(
+ [coordinate[0], coordinate[1], 1]
+ )
+ previousElement = coordinate
+
+ if cornerAddup[2] > 0:
+ flood_area = floodFillArea(
+ [(coord[0], coord[1]) for coord in currentAmenity]
)
+ for x, z in flood_area:
+ setBlock(blockType, x, groundLevel, z)
+ if (
+ element["tags"]["amenity"] == "parking"
+ and (x + z) % 8 == 0
+ and (x * z) % 32 != 0
+ ):
+ setBlock(
+ light_gray_concrete,
+ x,
+ groundLevel,
+ z,
+ overrideAll=True,
+ )
+
+ # Leisure
elif "leisure" in element["tags"]:
previousElement = (0, 0)
cornerAddup = (0, 0, 0)
currentLeisure = np.array([[0, 0]])
- for coordinate in element["nodes"]:
- leisureType = 39
- if (
- previousElement != (0, 0)
- and element["tags"]["leisure"] != "marina"
- ):
- if (
- element["tags"]["leisure"] == "park"
- or element["tags"]["leisure"] == "playground"
- or element["tags"]["leisure"] == "garden"
- ):
- leisureType = 32
- elif element["tags"]["leisure"] == "pitch":
- leisureType = 36
- elif element["tags"]["leisure"] == "swimming_pool":
- leisureType = 37
+ blockType = grass_block
+
+ leisure_type_mapping = {
+ "park": grass_block,
+ "playground": green_stained_hardened_clay,
+ "garden": grass_block,
+ "pitch": green_stained_hardened_clay,
+ "swimming_pool": water,
+ }
+
+ blockType = leisure_type_mapping.get(
+ element["tags"]["leisure"], grass_block
+ )
- for i in bresenham(
+ for coordinate in element["nodes"]:
+ if previousElement != (0, 0):
+ for coordBresenham in bresenham(
coordinate[0],
coordinate[1],
previousElement[0],
previousElement[1],
):
- if imgLanduse[i[1]][i[0]] == 0:
- imgLanduse[i[1]][i[0]] = leisureType
+ setBlock(
+ blockType,
+ coordBresenham[0],
+ groundLevel,
+ coordBresenham[1],
+ )
currentLeisure = np.append(
currentLeisure, [[coordinate[0], coordinate[1]]], axis=0
@@ -483,173 +1141,265 @@ def processData(data, args):
previousElement = (coordinate[0], coordinate[1])
if cornerAddup != (0, 0, 0):
- imgLanduse = floodFill(
- imgLanduse,
- round(cornerAddup[1] / cornerAddup[2]),
- round(cornerAddup[0] / cornerAddup[2]),
- leisureType,
- currentLeisure,
- minMaxDistX,
- minMaxDistY,
- )
-
+ polygon_coords = [
+ (coord[0], coord[1]) for coord in element["nodes"]
+ ]
+ filled_area = floodFillArea(polygon_coords)
+
+ for x, z in filled_area:
+ setBlock(blockType, x, groundLevel, z)
+
+ # Add decorative elements for parks and gardens
+ if element["tags"]["leisure"] in ["park", "garden"]:
+ if checkForWater(x, z):
+ continue
+ randomChoice = random.randint(
+ 0, 1000
+ ) # Random chance distribution
+ if randomChoice < 1: # Benches
+ setBlock(oak_log, x, groundLevel + 1, z)
+ setBlock(oak_log, x + 1, groundLevel + 1, z)
+ setBlock(oak_log, x - 1, groundLevel + 1, z)
+ elif randomChoice < 30: # Flowers
+ flower_choice = random.choice(
+ [
+ red_flower,
+ yellow_flower,
+ blue_flower,
+ white_flower,
+ ]
+ )
+ setBlock(flower_choice, x, groundLevel + 1, z)
+ elif randomChoice < 70: # Grass
+ setBlock(grass, x, groundLevel + 1, z)
+ elif randomChoice < 80: # Tree
+ createTree(x, groundLevel + 1, z, random.randint(1, 3))
+
+ # Waterways
elif "waterway" in element["tags"]:
previousElement = (0, 0)
+ waterwayWidth = 4
+
+ # Check for custom width in tags
+ if "width" in element["tags"]:
+ try:
+ waterwayWidth = int(element["tags"]["width"])
+ except ValueError:
+ waterwayWidth = int(float(element["tags"]["width"]))
+
for coordinate in element["nodes"]:
if previousElement != (0, 0) and not (
"layer" in element["tags"]
- and (
- element["tags"]["layer"] == "-1"
- or element["tags"]["layer"] == "-2"
- or element["tags"]["layer"] == "-3"
- )
+ and element["tags"]["layer"] in ["-1", "-2", "-3"]
):
- waterwayWidth = 4
- if "width" in element["tags"]:
- try:
- waterwayWidth = int(element["tags"]["width"])
- except Exception:
- waterwayWidth = int(float(element["tags"]["width"]))
-
- for i in bresenham(
+ for coordBresenham in bresenham(
coordinate[0],
coordinate[1],
previousElement[0],
previousElement[1],
):
for x in range(
- round(i[0] - waterwayWidth / 2),
- round(i[0] + waterwayWidth + 1 / 2),
+ round(coordBresenham[0] - waterwayWidth / 2),
+ round(coordBresenham[0] + waterwayWidth / 2) + 1,
):
- for y in range(
- round(i[1] - waterwayWidth / 2),
- round(i[1] + waterwayWidth + 1 / 2),
+ for z in range(
+ round(coordBresenham[1] - waterwayWidth / 2),
+ round(coordBresenham[1] + waterwayWidth / 2) + 1,
):
- if (
- x < minMaxDistX
- and y < minMaxDistY
- and img[y][x] != 13
- ):
- img[y][x] = 38
- previousElement = (coordinate[0], coordinate[1])
+ setBlock(water, x, groundLevel, z, overrideAll=True)
- elif "amenity" in element["tags"]:
- previousElement = (0, 0)
- cornerAddup = (0, 0, 0)
- currentAmenity = np.array([[0, 0]])
- amenityType = 20
- for coordinate in element["nodes"]:
- if previousElement != (0, 0) and (
- element["tags"]["amenity"] == "parking"
- or element["tags"]["amenity"] == "fountain"
- ):
- if element["tags"]["amenity"] == "parking":
- amenityType = 20
- elif element["tags"]["amenity"] == "fountain":
- amenityType = 21
-
- for i in bresenham(
- coordinate[0],
- coordinate[1],
- previousElement[0],
- previousElement[1],
- ):
- if imgLanduse[i[1]][i[0]] == 0:
- imgLanduse[i[1]][i[0]] = amenityType
-
- currentAmenity = np.append(
- currentAmenity, [[coordinate[0], coordinate[1]]], axis=0
- )
- cornerAddup = (
- cornerAddup[0] + coordinate[0],
- cornerAddup[1] + coordinate[1],
- cornerAddup[2] + 1,
- )
previousElement = (coordinate[0], coordinate[1])
- if amenityType == 21:
- amenityType = 37
- if cornerAddup != (0, 0, 0):
- imgLanduse = floodFill(
- imgLanduse,
- round(cornerAddup[1] / cornerAddup[2]),
- round(cornerAddup[0] / cornerAddup[2]),
- amenityType,
- currentAmenity,
- minMaxDistX,
- minMaxDistY,
+ # Bridges
+ elif "bridge" in element["tags"]:
+ if "layer" in element["tags"]:
+ bridge_height = int(element["tags"]["layer"])
+ else:
+ bridge_height = 1 # Default height if not specified
+
+ # Calculate the total length of the bridge
+ total_steps = sum(
+ len(
+ list(
+ bresenham(
+ element["nodes"][i][0],
+ element["nodes"][i][1],
+ element["nodes"][i - 1][0],
+ element["nodes"][i - 1][1],
+ )
+ )
)
+ for i in range(1, len(element["nodes"]))
+ )
- elif "bridge" in element["tags"]:
- previousElement = (0, 0)
- for coordinate in element["nodes"]:
- if previousElement != (0, 0):
- for i in bresenham(
+ half_steps = (
+ total_steps // 2
+ ) # Calculate midpoint for descending after rising
+
+ current_step = 0
+ for i, coordinate in enumerate(element["nodes"]):
+ if i > 0:
+ prev_coord = element["nodes"][i - 1]
+ for coordBresenham in bresenham(
coordinate[0],
coordinate[1],
- previousElement[0],
- previousElement[1],
+ prev_coord[0],
+ prev_coord[1],
):
- img[i[1]][i[0]] = 13
- previousElement = (coordinate[0], coordinate[1])
-
+ # Calculate the current height of the bridge
+ if current_step <= half_steps:
+ current_height = (
+ groundLevel + bridge_height + current_step // 5
+ ) # Rise for the first half
+ else:
+ current_height = (
+ groundLevel
+ + bridge_height
+ + (half_steps // 5)
+ - ((current_step - half_steps) // 5)
+ ) # Descend for the second half
+
+ setBlock(
+ light_gray_concrete,
+ coordBresenham[0],
+ current_height,
+ coordBresenham[1],
+ )
+ for offsetX, offsetZ in [
+ (-1, -1),
+ (1, -1),
+ (1, 1),
+ (-1, 1),
+ ]:
+ setBlock(
+ light_gray_concrete,
+ coordBresenham[0] + offsetX,
+ current_height,
+ coordBresenham[1] + offsetZ,
+ )
+
+ current_step += 1
+
+ # Railways
elif "railway" in element["tags"]:
- previousElement = (0, 0)
- for coordinate in element["nodes"]:
+ if element["tags"]["railway"] not in [
+ "proposed",
+ "abandoned",
+ "subway",
+ ]:
if (
- previousElement != (0, 0)
- and element["tags"]["railway"] != "proposed"
- and element["tags"]["railway"] != "abandoned"
+ "subway" in element["tags"]
+ and "yes" in element["tags"]["subway"]
):
- for i in bresenham(
- coordinate[0],
- coordinate[1],
- previousElement[0],
- previousElement[1],
- ):
- if i[0] < minMaxDistX and i[1] < minMaxDistY:
- img[i[1]][i[0]] = 14
- previousElement = (coordinate[0], coordinate[1])
-
+ continue
+
+ for i, coordinate in enumerate(element["nodes"]):
+ if i > 0:
+ prev_coord = element["nodes"][i - 1]
+ for coordBresenham in bresenham(
+ coordinate[0],
+ coordinate[1],
+ prev_coord[0],
+ prev_coord[1],
+ ):
+ # Determine direction and set rail shape accordingly
+ dx = coordBresenham[0] - prev_coord[0]
+ dz = coordBresenham[1] - prev_coord[1]
+
+ if dx == 0 and dz > 0:
+ shape = "north_south"
+ elif dx == 0 and dz < 0:
+ shape = "north_south"
+ elif dz == 0 and dx > 0:
+ shape = "east_west"
+ elif dz == 0 and dx < 0:
+ shape = "east_west"
+ elif dx > 0 and dz > 0:
+ shape = "south_east"
+ elif dx < 0 and dz < 0:
+ shape = "north_west"
+ elif dx > 0 and dz < 0:
+ shape = "north_east"
+ elif dx < 0 and dz > 0:
+ shape = "south_west"
+
+ rail_block = anvil.Block(
+ "minecraft", "rail", {"shape": shape}
+ )
+
+ setBlock(
+ iron_block,
+ coordBresenham[0],
+ groundLevel,
+ coordBresenham[1],
+ )
+ setBlock(
+ rail_block,
+ coordBresenham[0],
+ groundLevel + 1,
+ coordBresenham[1],
+ )
+
+ if coordBresenham[0] % 4 == 0:
+ setBlock(
+ oak_log,
+ coordBresenham[0],
+ groundLevel,
+ coordBresenham[1],
+ )
+
+ # Barriers
elif "barrier" in element["tags"]:
- previousElement = (0, 0)
- for coordinate in element["nodes"]:
- if previousElement != (0, 0):
- wallHeight = 1
- if (
- "height" in element["tags"]
- and str(element["tags"]["height"])
- .replace(".", "")
- .isnumeric()
- ):
- wallHeight = round(int(float(element["tags"]["height"])))
- if wallHeight > 3:
- wallHeight = 2
-
- for i in bresenham(
- coordinate[0],
- coordinate[1],
- previousElement[0],
- previousElement[1],
- ):
- if (
- str(img[i[1]][i[0]][0])[:1] != 5
- and str(img[i[1]][i[0]][0])[:1] != 6
- and str(img[i[1]][i[0]][0])[:1] != 7
+ if "bollard" in element["tags"]["barrier"]:
+ node_id = element["nodes"][0]
+ x = node_id[0]
+ z = node_id[1]
+ setBlock(cobblestone_wall, x, groundLevel + 1, z, overrideAll=True)
+ else:
+ if (
+ "height" in element["tags"]
+ and element["tags"]["height"].replace(".", "").isdigit()
+ ):
+ wallHeight = min(
+ 3, round(int(float(element["tags"]["height"])))
+ )
+ else:
+ wallHeight = 2
+
+ for i, coordinate in enumerate(element["nodes"]):
+ if i > 0:
+ prev_coord = element["nodes"][i - 1]
+ for coordBresenham in bresenham(
+ coordinate[0],
+ coordinate[1],
+ prev_coord[0],
+ prev_coord[1],
):
- img[i[1]][i[0]] = int("2" + str((wallHeight + 1)))
- previousElement = (coordinate[0], coordinate[1])
-
- ElementIncr += 1
-
- # Calculating layers
- mask = (imgLanduse != 0) & (img == 0)
- img[mask] = imgLanduse[mask]
+ # Build the barrier wall to the specified height
+ for y in range(
+ groundLevel + 1, groundLevel + wallHeight + 1
+ ):
+ setBlock(
+ cobblestone_wall,
+ coordBresenham[0],
+ y,
+ coordBresenham[1],
+ overrideAll=True,
+ )
+
+ # Add an optional top to the barrier if the height is more than 1
+ if wallHeight > 1:
+ setBlock(
+ stone_brick_slab,
+ coordBresenham[0],
+ groundLevel + wallHeight + 1,
+ coordBresenham[1],
+ overrideAll=True,
+ )
+
+ saveRegions()
print(
- f"Processing finished in {(time() - processingStartTime):.2f} seconds"
- + f" ({((time() - processingStartTime) / 60):.2f} minutes)"
+ f"Processing finished in {(time() - startTime):.2f} seconds"
+ + f" ({((time() - startTime) / 60):.2f} minutes)"
)
- if args.debug:
- imwrite("arnis-debug-map.png", img)
- return np.flip(img, axis=1)
diff --git a/src/tree.py b/src/tree.py
index b0ae578..7ce460d 100644
--- a/src/tree.py
+++ b/src/tree.py
@@ -1,12 +1,8 @@
-from .main import (
+from .processData import (
setBlock,
fillBlocks,
- oak_leaves,
- oak_log,
- spruce_log,
- birch_leaves,
- birch_log,
)
+from .blockDefinitions import *
def round1(material, x, y, z):
@@ -50,54 +46,54 @@ def round3(material, x, y, z):
setBlock(material, x - 1, y, z - 3)
-def createTree(x, z, typetree=1):
+def createTree(x, y, z, typetree=1):
if typetree == 1: # Oak tree
- fillBlocks(oak_log, x, 2, z, x, 10, z)
- fillBlocks(oak_leaves, x - 1, 5, z, x - 1, 11, z)
- fillBlocks(oak_leaves, x + 1, 5, z, x + 1, 11, z)
- fillBlocks(oak_leaves, x, 5, z - 1, x, 11, z - 1)
- fillBlocks(oak_leaves, x, 5, z + 1, x, 11, z + 1)
- fillBlocks(oak_leaves, x, 11, z, x, 12, z)
- round1(oak_leaves, x, 10, z)
- round1(oak_leaves, x, 9, z)
- round1(oak_leaves, x, 8, z)
- round1(oak_leaves, x, 7, z)
- round1(oak_leaves, x, 6, z)
- round1(oak_leaves, x, 5, z)
- round2(oak_leaves, x, 9, z)
- round2(oak_leaves, x, 8, z)
- round2(oak_leaves, x, 7, z)
- round2(oak_leaves, x, 6, z)
- round3(oak_leaves, x, 8, z)
- round3(oak_leaves, x, 7, z)
+ fillBlocks(oak_log, x, y, z, x, y + 8, z)
+ fillBlocks(oak_leaves, x - 1, y + 3, z, x - 1, y + 9, z)
+ fillBlocks(oak_leaves, x + 1, y + 3, z, x + 1, y + 9, z)
+ fillBlocks(oak_leaves, x, y + 3, z - 1, x, y + 9, z - 1)
+ fillBlocks(oak_leaves, x, y + 3, z + 1, x, y + 9, z + 1)
+ fillBlocks(oak_leaves, x, y + 9, z, x, y + 10, z)
+ round1(oak_leaves, x, y + 8, z)
+ round1(oak_leaves, x, y + 7, z)
+ round1(oak_leaves, x, y + 6, z)
+ round1(oak_leaves, x, y + 5, z)
+ round1(oak_leaves, x, y + 4, z)
+ round1(oak_leaves, x, y + 3, z)
+ round2(oak_leaves, x, y + 7, z)
+ round2(oak_leaves, x, y + 6, z)
+ round2(oak_leaves, x, y + 5, z)
+ round2(oak_leaves, x, y + 4, z)
+ round3(oak_leaves, x, y + 6, z)
+ round3(oak_leaves, x, y + 5, z)
elif typetree == 2: # Spruce tree
- fillBlocks(spruce_log, x, 2, z, x, 11, z)
- fillBlocks(birch_leaves, x - 1, 5, z, x - 1, 12, z)
- fillBlocks(birch_leaves, x + 1, 5, z, x + 1, 12, z)
- fillBlocks(birch_leaves, x, 5, z - 1, x, 12, z - 1)
- fillBlocks(birch_leaves, x, 5, z + 1, x, 12, z + 1)
- setBlock(birch_leaves, x, 12, z)
- round1(birch_leaves, x, 11, z)
- round1(birch_leaves, x, 9, z)
- round1(birch_leaves, x, 8, z)
- round1(birch_leaves, x, 6, z)
- round1(birch_leaves, x, 5, z)
- round2(birch_leaves, x, 8, z)
- round2(birch_leaves, x, 5, z)
+ fillBlocks(spruce_log, x, y, z, x, y + 9, z)
+ fillBlocks(birch_leaves, x - 1, y + 3, z, x - 1, y + 10, z)
+ fillBlocks(birch_leaves, x + 1, y + 3, z, x + 1, y + 10, z)
+ fillBlocks(birch_leaves, x, y + 3, z - 1, x, y + 10, z - 1)
+ fillBlocks(birch_leaves, x, y + 3, z + 1, x, y + 10, z + 1)
+ setBlock(birch_leaves, x, y + 10, z)
+ round1(birch_leaves, x, y + 9, z)
+ round1(birch_leaves, x, y + 7, z)
+ round1(birch_leaves, x, y + 6, z)
+ round1(birch_leaves, x, y + 4, z)
+ round1(birch_leaves, x, y + 3, z)
+ round2(birch_leaves, x, y + 6, z)
+ round2(birch_leaves, x, y + 3, z)
elif typetree == 3: # Birch tree
- fillBlocks(birch_log, x, 2, z, x, 8, z)
- fillBlocks(birch_leaves, x - 1, 4, z, x - 1, 9, z)
- fillBlocks(birch_leaves, x + 1, 4, z, x + 1, 9, z)
- fillBlocks(birch_leaves, x, 4, z - 1, x, 9, z - 1)
- fillBlocks(birch_leaves, x, 4, z + 1, x, 9, z + 1)
- fillBlocks(birch_leaves, x, 9, z, x, 10, z)
- round1(birch_leaves, x, 8, z)
- round1(birch_leaves, x, 7, z)
- round1(birch_leaves, x, 6, z)
- round1(birch_leaves, x, 5, z)
- round1(birch_leaves, x, 4, z)
- round2(birch_leaves, x, 4, z)
- round2(birch_leaves, x, 5, z)
- round2(birch_leaves, x, 6, z)
+ fillBlocks(birch_log, x, y, z, x, y + 6, z)
+ fillBlocks(birch_leaves, x - 1, y + 2, z, x - 1, y + 7, z)
+ fillBlocks(birch_leaves, x + 1, y + 2, z, x + 1, y + 7, z)
+ fillBlocks(birch_leaves, x, y + 2, z - 1, x, y + 7, z - 1)
+ fillBlocks(birch_leaves, x, y + 2, z + 1, x, y + 7, z + 1)
+ fillBlocks(birch_leaves, x, y + 7, z, x, y + 8, z)
+ round1(birch_leaves, x, y + 6, z)
+ round1(birch_leaves, x, y + 5, z)
+ round1(birch_leaves, x, y + 4, z)
+ round1(birch_leaves, x, y + 3, z)
+ round1(birch_leaves, x, y + 2, z)
+ round2(birch_leaves, x, y + 2, z)
+ round2(birch_leaves, x, y + 3, z)
+ round2(birch_leaves, x, y + 4, z)